├── .eleventy.cjs ├── .gitignore ├── .prettierrc.json ├── README.md ├── custom-elements.json ├── custom_typings └── pdf.d.ts ├── demo ├── f1040.pdf └── index.html ├── docs-src ├── _README.md ├── _data │ └── api.11tydata.js ├── _includes │ ├── example.11ty.cjs │ ├── footer.11ty.cjs │ ├── header.11ty.cjs │ ├── nav.11ty.cjs │ ├── page.11ty.cjs │ └── relative-path.cjs ├── _includes_old │ ├── example.11ty.cjs │ ├── footer.11ty.cjs │ ├── header.11ty.cjs │ ├── nav.11ty.cjs │ ├── page.11ty.cjs │ └── relative-path.cjs ├── api.11ty.cjs ├── docs.css ├── examples │ ├── index.md │ └── name-property.md ├── index.md ├── install.md ├── package-lock.json └── package.json ├── docs ├── _README │ └── index.html ├── _includes_old │ ├── example │ │ └── index.html │ ├── footer │ │ └── index.html │ ├── header │ │ └── index.html │ ├── nav │ │ └── index.html │ └── page │ │ └── index.html ├── api │ └── index.html ├── docs.css ├── examples │ ├── index.html │ └── name-property │ │ └── index.html ├── index.html ├── install │ └── index.html ├── pdf-viewer.bundled.js └── prism-okaidia.css ├── package-lock.json ├── package.json ├── src ├── components │ ├── pdf-viewer-display.ts │ ├── pdf-viewer-toolbar.ts │ ├── pdf-viewer.ts │ └── viewer-context.ts ├── demo │ └── pdf-viewer-demo.ts ├── index.ts └── lib │ └── styles.ts └── tsconfig.json /.eleventy.cjs: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight'); 2 | 3 | module.exports = function (eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight); 5 | eleventyConfig.addPassthroughCopy("docs-src/docs.css"); 6 | eleventyConfig.addPassthroughCopy("docs-src/.nojekyll"); 7 | return { 8 | dir: { 9 | input: 'docs-src', 10 | output: 'docs', 11 | }, 12 | templateExtensionAliases: { 13 | '11ty.cjs': '11ty.js', 14 | '11tydata.cjs': '11tydata.js', 15 | }, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | # build output 4 | /lib/ 5 | /components/ 6 | /*.d.ts 7 | /*.d.ts.map 8 | /*.js 9 | /*.js.map 10 | /demo/*.d.ts 11 | /demo/*.d.ts.map 12 | /demo/*.js 13 | /demo/*.js.map 14 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": false 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `` 2 | 3 | `` is a custom HTML element that displays PDFs, much like an `` tag. 4 | 5 | `` is a drop-in PDF viewer. It works in plain HTML documents, and in frameworks with declararive templating like React, Vue, Angular, Lit, or Svelte. 6 | 7 | `` is built with [PDF.js](https://github.com/mozilla/pdf.js), which renders PDFs with JavaScript. PDF.js is maintained by Mozilla and powers Firefox's built-in PDF viewer. 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm i pdf-viewer-element 13 | ``` 14 | 15 | ## Use 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | 23 | ``` 24 | 25 | 26 | ### Or from a CDN like unpkg.com: 27 | 28 | ```html 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | 36 | 37 | # Goals 38 | 39 | * Simple drop-in PDF viewer with no configuration required 40 | * No build steps required 41 | * Works from a CDN 42 | * Optional configuration for power-users: 43 | * Exposes most useful pdf.js options 44 | * Customization via DOM: CSS variables, slots, parts, etc. 45 | * Adapt pdf.js to the DOM: ie, pipe events back through elements 46 | 47 | # Project Goals 48 | 49 | Why make this? 50 | 51 | * Learn pdf.js 52 | * Create a useful framework-agnostic element for the web ecosystem 53 | * Gain experience with tricky module, worker, and asynchronous timing issues within web component wrappers 54 | * Convince pdf.js to distribute modules and web components directly? 55 | 56 | # TODO 57 | 58 | * Controls replaceable via ``s 59 | * Tests 60 | * Pipe events from pdf.js event-bus into DOM elements 61 | * Options: rotation, auto-size 62 | * Use ResizeObserver for auto-size 63 | * Use IntersectionObserver for laziness 64 | -------------------------------------------------------------------------------- /custom-elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "experimental", 3 | "tags": [ 4 | { 5 | "name": "pdf-viewer-app-bar", 6 | "path": "./src/demo/pdf-viewer-demo.ts", 7 | "properties": [ 8 | { 9 | "name": "styles", 10 | "default": "[\"styles\",null]" 11 | }, 12 | { 13 | "name": "prominent", 14 | "type": "boolean" 15 | }, 16 | { 17 | "name": "dense", 18 | "type": "boolean" 19 | }, 20 | { 21 | "name": "centerTitle", 22 | "type": "boolean" 23 | }, 24 | { 25 | "name": "scrollTarget", 26 | "type": "HTMLElement | Window" 27 | } 28 | ] 29 | }, 30 | { 31 | "name": "pdf-viewer-demo", 32 | "path": "./src/demo/pdf-viewer-demo.ts", 33 | "attributes": [ 34 | { 35 | "name": "src", 36 | "type": "string", 37 | "default": "\"./f1040.pdf\"" 38 | }, 39 | { 40 | "name": "multi-page", 41 | "type": "boolean", 42 | "default": "false" 43 | }, 44 | { 45 | "name": "page", 46 | "type": "number", 47 | "default": "1" 48 | } 49 | ], 50 | "properties": [ 51 | { 52 | "name": "src", 53 | "attribute": "src", 54 | "type": "string", 55 | "default": "\"./f1040.pdf\"" 56 | }, 57 | { 58 | "name": "multiPage", 59 | "attribute": "multi-page", 60 | "type": "boolean", 61 | "default": "false" 62 | }, 63 | { 64 | "name": "page", 65 | "attribute": "page", 66 | "type": "number", 67 | "default": "1" 68 | }, 69 | { 70 | "name": "styles", 71 | "type": "CSSResult", 72 | "default": "\"css`\\n pdf-viewer-app-bar {\\n height: 100vh;\\n background: #efefef;\\n }\\n #content {\\n display: flex;\\n flex-direction: column;\\n height: 100%;\\n }\\n #controls {\\n background: white;\\n display: flex;\\n flex-direction: row;\\n align-items: baseline;\\n padding: 16px;\\n }\\n #demo-container {\\n flex: auto;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n }\\n pdf-viewer {\\n width: 800px;\\n height: 800px;\\n }\\n `\"" 73 | } 74 | ] 75 | }, 76 | { 77 | "name": "pdf-viewer", 78 | "path": "./src/pdf-viewer.ts", 79 | "description": "A web component that displays PDFs", 80 | "attributes": [ 81 | { 82 | "name": "src", 83 | "type": "string | undefined" 84 | }, 85 | { 86 | "name": "page", 87 | "description": "The current 1-based page number.", 88 | "type": "number", 89 | "default": "1" 90 | }, 91 | { 92 | "name": "multi-page", 93 | "description": "Whether multiple pages should render. Single page rendering is much faster.", 94 | "type": "boolean", 95 | "default": "false" 96 | }, 97 | { 98 | "name": "scale", 99 | "type": "number | \"fit\"", 100 | "default": "\"fit\"" 101 | }, 102 | { 103 | "name": "zoom", 104 | "type": "number", 105 | "default": "1" 106 | }, 107 | { 108 | "name": "documentTitle", 109 | "type": "string | undefined" 110 | } 111 | ], 112 | "properties": [ 113 | { 114 | "name": "styles", 115 | "type": "CSSResult[]", 116 | "default": "[null,\"styles\"]" 117 | }, 118 | { 119 | "name": "src", 120 | "attribute": "src", 121 | "type": "string | undefined" 122 | }, 123 | { 124 | "name": "page", 125 | "attribute": "page", 126 | "description": "The current 1-based page number.", 127 | "type": "number", 128 | "default": "1" 129 | }, 130 | { 131 | "name": "pageCount", 132 | "description": "Total page count of the current document." 133 | }, 134 | { 135 | "name": "multiPage", 136 | "attribute": "multi-page", 137 | "description": "Whether multiple pages should render. Single page rendering is much faster.", 138 | "type": "boolean", 139 | "default": "false" 140 | }, 141 | { 142 | "name": "scale", 143 | "attribute": "scale", 144 | "type": "number | \"fit\"", 145 | "default": "\"fit\"" 146 | }, 147 | { 148 | "name": "zoom", 149 | "attribute": "zoom", 150 | "type": "number", 151 | "default": "1" 152 | }, 153 | { 154 | "name": "documentTitle", 155 | "attribute": "documentTitle", 156 | "type": "string | undefined" 157 | } 158 | ], 159 | "events": [ 160 | { 161 | "name": "load" 162 | } 163 | ], 164 | "cssProperties": [ 165 | { 166 | "name": "--pdf-viewer-top-bar-height", 167 | "default": "\"48px\"" 168 | }, 169 | { 170 | "name": "--pdf-viewer-page-shadow", 171 | "default": "\"2px 2px 2px 1px rgba(0, 0, 0, 0.2)\"" 172 | }, 173 | { 174 | "name": "--pdf-viewer-background", 175 | "default": "\"gray\"" 176 | } 177 | ] 178 | } 179 | ] 180 | } -------------------------------------------------------------------------------- /custom_typings/pdf.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@bundled-es-modules/pdfjs-dist' { 2 | const x: typeof import('pdfjs-dist'); 3 | export default x; 4 | } 5 | declare module '@bundled-es-modules/pdfjs-dist/web/pdf_viewer' { 6 | // import * as pdf from 'pdfjs-dist'; 7 | // export const PDFViewer: typeof pdf.PDFJS.PDFViewer; 8 | // const viewer: typeof import('pdfjs-dist').PDFJS; 9 | const viewer: any; 10 | export default viewer; 11 | } 12 | -------------------------------------------------------------------------------- /demo/f1040.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinfagnani/pdf-viewer-element/54a68d1086eacce03dfd023738099b70d0c6f76b/demo/f1040.pdf -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs-src/_README.md: -------------------------------------------------------------------------------- 1 | This directory containts the sources for the static site contained in the /docs/ directory. The site is based on the [eleventy](11ty.dev) static site generator. 2 | 3 | The site is intended to be used with GitHub pages. To enable the site go to the GitHub settings and change the GitHub Pages "Source" setting to "master branch /docs folder". 4 | 5 | To view the site locally, run `npm run docs:serve`. 6 | 7 | To edit the site, add to or edit the files in this directory then run `npm run docs` to build the site. The built files must be checked in and pushed to GitHub to appear on GitHub pages. 8 | -------------------------------------------------------------------------------- /docs-src/_data/api.11tydata.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = () => { 4 | const customElements = JSON.parse(fs.readFileSync('custom-elements.json', 'utf-8')); 5 | return { 6 | customElements, 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /docs-src/_includes/example.11ty.cjs: -------------------------------------------------------------------------------- 1 | const page = require('./page.11ty.cjs'); 2 | const relative = require('./relative-path.cjs'); 3 | 4 | /** 5 | * This template extends the page template and adds an examples list. 6 | */ 7 | module.exports = function(data) { 8 | 9 | return page({ 10 | ...data, 11 | content: renderExample(data), 12 | }); 13 | }; 14 | 15 | const renderExample = ({name, content, collections, page}) => { 16 | return ` 17 |

Example: ${name}

18 |
19 | 30 |
31 | ${content} 32 |
33 |
34 | `; 35 | }; 36 | -------------------------------------------------------------------------------- /docs-src/_includes/footer.11ty.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | return ` 3 | `; 9 | }; 10 | -------------------------------------------------------------------------------- /docs-src/_includes/header.11ty.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | return ` 3 |
4 |

<pdf-viewer>

5 |

A PDF viewer for the web

6 |
`; 7 | }; 8 | -------------------------------------------------------------------------------- /docs-src/_includes/nav.11ty.cjs: -------------------------------------------------------------------------------- 1 | const relative = require('./relative-path.cjs'); 2 | 3 | module.exports = function({page}) { 4 | return ` 5 | `; 11 | }; 12 | -------------------------------------------------------------------------------- /docs-src/_includes/page.11ty.cjs: -------------------------------------------------------------------------------- 1 | const header = require('./header.11ty.cjs'); 2 | const footer = require('./footer.11ty.cjs'); 3 | const nav = require('./nav.11ty.cjs'); 4 | const relative = require('./relative-path.cjs'); 5 | 6 | module.exports = function(data) { 7 | const {title, page, content} = data; 8 | return ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | ${title} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ${header()} 24 | ${nav(data)} 25 |
26 |
27 | ${content} 28 |
29 |
30 | ${footer()} 31 | 32 | `; 33 | }; 34 | -------------------------------------------------------------------------------- /docs-src/_includes/relative-path.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path').posix; 2 | 3 | module.exports = (base, p) => { 4 | const relativePath = path.relative(base, p); 5 | if (p.endsWith('/') && !relativePath.endsWith('/') && relativePath !== '') { 6 | return relativePath + '/'; 7 | } 8 | return relativePath; 9 | }; 10 | -------------------------------------------------------------------------------- /docs-src/_includes_old/example.11ty.cjs: -------------------------------------------------------------------------------- 1 | const page = require('./page.11ty.cjs'); 2 | const relative = require('./relative-path.cjs'); 3 | 4 | /** 5 | * This template extends the page template and adds an examples list. 6 | */ 7 | module.exports = function(data) { 8 | 9 | return page({ 10 | ...data, 11 | content: renderExample(data), 12 | }); 13 | }; 14 | 15 | const renderExample = ({name, content, collections, page}) => { 16 | return ` 17 |

Example: ${name}

18 |
19 | 30 |
31 | ${content} 32 |
33 |
34 | `; 35 | }; 36 | -------------------------------------------------------------------------------- /docs-src/_includes_old/footer.11ty.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | return ` 3 | `; 9 | }; 10 | -------------------------------------------------------------------------------- /docs-src/_includes_old/header.11ty.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | return ` 3 |
4 |

<pdf-viewer>

5 |

A web component just for me.

6 |
`; 7 | }; 8 | -------------------------------------------------------------------------------- /docs-src/_includes_old/nav.11ty.cjs: -------------------------------------------------------------------------------- 1 | const relative = require('./relative-path.cjs'); 2 | 3 | module.exports = function({page}) { 4 | return ` 5 | `; 11 | }; 12 | -------------------------------------------------------------------------------- /docs-src/_includes_old/page.11ty.cjs: -------------------------------------------------------------------------------- 1 | const header = require('./header.11ty.cjs'); 2 | const footer = require('./footer.11ty.cjs'); 3 | const nav = require('./nav.11ty.cjs'); 4 | const relative = require('./relative-path.cjs'); 5 | 6 | module.exports = function(data) { 7 | const {title, page, content} = data; 8 | return ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | ${title} 16 | 17 | 18 | 19 | 20 | 21 | 22 | ${header()} 23 | ${nav(data)} 24 |
25 |
26 | ${content} 27 |
28 |
29 | ${footer()} 30 | 31 | `; 32 | }; 33 | -------------------------------------------------------------------------------- /docs-src/_includes_old/relative-path.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path').posix; 2 | 3 | module.exports = (base, p) => { 4 | const relativePath = path.relative(base, p); 5 | if (p.endsWith('/') && !relativePath.endsWith('/') && relativePath !== '') { 6 | return relativePath + '/'; 7 | } 8 | return relativePath; 9 | }; 10 | -------------------------------------------------------------------------------- /docs-src/api.11ty.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This page generates its content from the custom-element.json file as read by 3 | * the _data/api.11tydata.js script. 4 | */ 5 | module.exports = class Docs { 6 | data() { 7 | return { 8 | layout: 'page.11ty.cjs', 9 | title: ' ⌲ Docs', 10 | }; 11 | } 12 | 13 | render(data) { 14 | const customElements = data.api['11tydata'].customElements; 15 | const tags = customElements.tags; 16 | return ` 17 |

API

18 | ${tags.map((tag) => ` 19 |

<${tag.name}>

20 |
21 | ${tag.description} 22 |
23 | ${renderTable( 24 | 'Attributes', 25 | ['name', 'description', 'type', 'default'], 26 | tag.attributes)} 27 | ${renderTable( 28 | 'Properties', 29 | ['name', 'attribute', 'description', 'type', 'default'], 30 | tag.properties)} 31 | ${/* 32 | * Methods are not output by web-component-analyzer yet (a bug), so 33 | * this is a placeholder so that at least _something_ will be output 34 | * when that is fixed, and element maintainers will hopefully have a 35 | * signal to update this file to add the neccessary columns. 36 | */ 37 | renderTable( 38 | 'Methods', 39 | ['name', 'description'], 40 | tag.methods)} 41 | ${renderTable( 42 | 'Events', 43 | ['name', 'description'], 44 | tag.events)} 45 | ${renderTable( 46 | 'Slots', 47 | ['name', 'description'], 48 | tag.slots)} 49 | ${renderTable( 50 | 'CSS Shadow Parts', 51 | ['name', 'description'], 52 | tag.cssParts)} 53 | ${renderTable( 54 | 'CSS Custom Properties', 55 | ['name', 'description'], 56 | tag.cssProperties)} 57 | `).join('')} 58 | `; 59 | } 60 | } 61 | 62 | /** 63 | * Renders a table of data, plucking the given properties from each item in 64 | * `data`. 65 | */ 66 | const renderTable = (name, properties, data) => { 67 | if (data === undefined) { 68 | return '' 69 | } 70 | return ` 71 |

${name}

72 | 73 | 74 | ${properties.map((p) => ``).join('')} 75 | 76 | ${data.map((i) => ` 77 | 78 | ${properties.map((p) => ``).join('')} 79 | 80 | `).join('')} 81 |
${capitalize(p)}
${i[p]}
82 | ` 83 | }; 84 | 85 | const capitalize = (s) => s[0].toUpperCase() + s.substring(1); 86 | -------------------------------------------------------------------------------- /docs-src/docs.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | color: #333; 8 | font-family: 'Open Sans', arial, sans-serif; 9 | min-width: min-content; 10 | min-height: 100vh; 11 | font-size: 18px; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: stretch; 15 | } 16 | 17 | #main-wrapper { 18 | flex-grow: 1; 19 | } 20 | 21 | main { 22 | max-width: 1024px; 23 | margin: 0 auto; 24 | } 25 | 26 | a:visited { 27 | color: inherit; 28 | } 29 | 30 | header { 31 | width: 100%; 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | justify-content: center; 36 | height: 360px; 37 | margin: 0; 38 | background: linear-gradient(0deg, rgba(9,9,121,1) 0%, rgba(0,212,255,1) 100%); 39 | color: white; 40 | } 41 | 42 | footer { 43 | width: 100%; 44 | min-height: 120px; 45 | background: gray; 46 | color: white; 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: center; 50 | padding: 12px; 51 | margin-top: 64px; 52 | } 53 | 54 | h1 { 55 | font-size: 2.5em; 56 | font-weight: 400; 57 | } 58 | 59 | h2 { 60 | font-size: 1.6em; 61 | font-weight: 300; 62 | margin: 64px 0 12px; 63 | } 64 | 65 | h3 { 66 | font-weight: 300; 67 | } 68 | 69 | header h1 { 70 | width: auto; 71 | font-size: 2.8em; 72 | margin: 0; 73 | } 74 | 75 | header h2 { 76 | width: auto; 77 | margin: 0; 78 | } 79 | 80 | nav { 81 | display: grid; 82 | width: 100%; 83 | max-width: 100%; 84 | grid-template-columns: repeat(auto-fit, 240px); 85 | justify-content: center; 86 | border-bottom: 1px solid #efefef; 87 | } 88 | 89 | nav > a { 90 | color: #444; 91 | display: block; 92 | flex: 1; 93 | font-size: 18px; 94 | padding: 20px 0; 95 | text-align: center; 96 | text-decoration: none; 97 | } 98 | 99 | nav > a:hover { 100 | text-decoration: underline; 101 | } 102 | 103 | nav.collection { 104 | border: none; 105 | } 106 | 107 | nav.collection > ul { 108 | padding: 0; 109 | list-style: none; 110 | } 111 | 112 | nav.collection > ul > li { 113 | padding: 4px 0; 114 | } 115 | 116 | nav.collection > ul > li.selected { 117 | font-weight: 600; 118 | } 119 | 120 | nav.collection a { 121 | text-decoration: none; 122 | } 123 | 124 | nav.collection a:hover { 125 | text-decoration: underline; 126 | } 127 | 128 | section.columns { 129 | display: grid; 130 | grid-template-columns: repeat(auto-fit, minmax(400px, 488px)); 131 | grid-gap: 48px; 132 | justify-content: center; 133 | } 134 | 135 | section.columns > div { 136 | flex: 1; 137 | } 138 | 139 | section.examples { 140 | display: grid; 141 | grid-template-columns: 240px minmax(400px, 784px); 142 | grid-gap: 48px; 143 | justify-content: center; 144 | } 145 | 146 | section.examples h2:first-of-type { 147 | margin-top: 0; 148 | } 149 | 150 | table { 151 | width: 100%; 152 | border-collapse: collapse; 153 | } 154 | th { 155 | font-weight: 600; 156 | } 157 | 158 | td, th { 159 | border: solid 1px #aaa; 160 | padding: 4px; 161 | text-align: left; 162 | } 163 | -------------------------------------------------------------------------------- /docs-src/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.11ty.cjs 3 | title: ⌲ Examples ⌲ Basic 4 | tags: example 5 | name: Basic 6 | description: A basic example 7 | --- 8 | 9 | 15 | 16 |

This is child content

17 |
18 | 19 |

CSS

20 | 21 | ```css 22 | p { 23 | border: solid 1px blue; 24 | padding: 8px; 25 | } 26 | ``` 27 | 28 |

HTML

29 | 30 | ```html 31 | 32 |

This is child content

33 |
34 | ``` 35 | -------------------------------------------------------------------------------- /docs-src/examples/name-property.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: example.11ty.cjs 3 | title: ⌲ Examples ⌲ Name Property 4 | tags: example 5 | name: Name Property 6 | description: Setting the name property 7 | --- 8 | 9 | 10 | 11 |

HTML

12 | 13 | ```html 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /docs-src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page.11ty.cjs 3 | title: ⌲ Home 4 | --- 5 | 6 | # <pdf-viewer> 7 | 8 | `` is an awesome element. It's a great introduction to building web components with LitElement, with nice documentation site as well. 9 | 10 | ## As easy as HTML 11 | 12 |
13 |
14 | 15 | `` is just an HTML element. You can it anywhere you can use HTML! 16 | 17 | ```html 18 | 19 | ``` 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 | ## Configure with attributes 30 | 31 |
32 |
33 | 34 | `` can be configured with attributed in plain HTML. 35 | 36 | ```html 37 | 38 | ``` 39 | 40 |
41 |
42 | 43 | 44 | 45 |
46 |
47 | 48 | ## Declarative rendering 49 | 50 |
51 |
52 | 53 | `` can be used with declarative rendering libraries like Angular, React, Vue, and lit-html 54 | 55 | ```js 56 | import {html, render} from 'lit-html'; 57 | 58 | const name="lit-html"; 59 | 60 | render(html` 61 |

This is a <pdf-viewer>

62 | 63 | `, document.body); 64 | ``` 65 | 66 |
67 |
68 | 69 |

This is a <pdf-viewer>

70 | 71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /docs-src/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page.11ty.cjs 3 | title: ⌲ Install 4 | --- 5 | 6 | # Install 7 | 8 | `` is distributed on npm, so you can install it locally or use it via npm CDNs like unpkg.com. 9 | 10 | ## Local Installation 11 | 12 | ```bash 13 | npm i pdf-viewer 14 | ``` 15 | 16 | ## CDN 17 | 18 | npm CDNs like [unpkg.com]() can directly serve files that have been published to npm. This works great for standard JavaScript modules that the browser can load natively. 19 | 20 | For this element to work from unpkg.com specifically, you need to include the `?module` query parameter, which tells unpkg.com to rewrite "bare" module specificers to full URLs. 21 | 22 | ### HTML 23 | ```html 24 | 25 | ``` 26 | 27 | ### JavaScript 28 | ```html 29 | import {MyElement} from 'https://unpkg.com/pdf-viewer?module'; 30 | ``` 31 | -------------------------------------------------------------------------------- /docs-src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /docs-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /docs/_README/index.html: -------------------------------------------------------------------------------- 1 |

This directory containts the sources for the static site contained in the /docs/ directory. The site is based on the eleventy static site generator.

2 |

The site is intended to be used with GitHub pages. To enable the site go to the GitHub settings and change the GitHub Pages "Source" setting to "master branch /docs folder".

3 |

To view the site locally, run npm run docs:serve.

4 |

To edit the site, add to or edit the files in this directory then run npm run docs to build the site. The built files must be checked in and pushed to GitHub to appear on GitHub pages.

5 | -------------------------------------------------------------------------------- /docs/_includes_old/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | undefined 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

<pdf-viewer>

18 |

A web component just for me.

19 |
20 | 21 | 27 |
28 |
29 | 30 |

Example: undefined

31 |
32 | 45 |
46 | undefined 47 |
48 |
49 | 50 |
51 |
52 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /docs/_includes_old/footer/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_includes_old/header/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

<pdf-viewer>

4 |

A web component just for me.

5 |
-------------------------------------------------------------------------------- /docs/_includes_old/nav/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_includes_old/page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | undefined 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

<pdf-viewer>

18 |

A web component just for me.

19 |
20 | 21 | 27 |
28 |
29 | undefined 30 |
31 |
32 | 33 | 39 | 40 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <pdf-viewer> ⌲ Docs 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

<my-element>

19 |

A web component just for me.

20 |
21 | 22 | 28 |
29 |
30 | 31 |

API

32 | 33 |

<pdf-viewer-app-bar>

34 |
35 | undefined 36 |
37 | 38 | 39 |

Properties

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
NameAttributeDescriptionTypeDefault
stylesundefinedundefinedundefined["styles",null]
prominentundefinedundefinedbooleanundefined
denseundefinedundefinedbooleanundefined
centerTitleundefinedundefinedbooleanundefined
scrollTargetundefinedundefinedHTMLElement | Windowundefined
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

<pdf-viewer-demo>

74 |
75 | undefined 76 |
77 | 78 |

Attributes

79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
NameDescriptionTypeDefault
srcundefinedstring"./f1040.pdf"
multi-pageundefinedbooleanfalse
pageundefinednumber1
97 | 98 | 99 |

Properties

100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
NameAttributeDescriptionTypeDefault
srcsrcundefinedstring"./f1040.pdf"
multiPagemulti-pageundefinedbooleanfalse
pagepageundefinednumber1
stylesundefinedundefinedCSSResult"css`\n pdf-viewer-app-bar {\n height: 100vh;\n background: #efefef;\n }\n #content {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n #controls {\n background: white;\n display: flex;\n flex-direction: row;\n align-items: baseline;\n padding: 16px;\n }\n #demo-container {\n flex: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n pdf-viewer {\n width: 800px;\n height: 800px;\n }\n `"
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |

<pdf-viewer>

130 |
131 | A web component that displays PDFs 132 |
133 | 134 |

Attributes

135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 |
NameDescriptionTypeDefault
srcundefinedstring | undefinedundefined
pageThe current 1-based page number.number1
multi-pageWhether multiple pages should render. Single page rendering is much faster.booleanfalse
scaleundefinednumber | "fit""fit"
zoomundefinednumber1
documentTitleundefinedstring | undefinedundefined
165 | 166 | 167 |

Properties

168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 |
NameAttributeDescriptionTypeDefault
stylesundefinedundefinedCSSResult[][null,"styles"]
srcsrcundefinedstring | undefinedundefined
pagepageThe current 1-based page number.number1
pageCountundefinedTotal page count of the current document.undefinedundefined
multiPagemulti-pageWhether multiple pages should render. Single page rendering is much faster.booleanfalse
scalescaleundefinednumber | "fit""fit"
zoomzoomundefinednumber1
documentTitledocumentTitleundefinedstring | undefinedundefined
206 | 207 | 208 | 209 |

Events

210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 |
NameDescription
loadundefined
220 | 221 | 222 | 223 | 224 |

CSS Custom Properties

225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 |
NameDescription
--pdf-viewer-top-bar-heightundefined
--pdf-viewer-page-shadowundefined
--pdf-viewer-backgroundundefined
243 | 244 | 245 | 246 |
247 |
248 | 249 | 255 | 256 | -------------------------------------------------------------------------------- /docs/docs.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | color: #333; 8 | font-family: 'Open Sans', arial, sans-serif; 9 | min-width: min-content; 10 | min-height: 100vh; 11 | font-size: 18px; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: stretch; 15 | } 16 | 17 | #main-wrapper { 18 | flex-grow: 1; 19 | } 20 | 21 | main { 22 | max-width: 1024px; 23 | margin: 0 auto; 24 | } 25 | 26 | a:visited { 27 | color: inherit; 28 | } 29 | 30 | header { 31 | width: 100%; 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | justify-content: center; 36 | height: 360px; 37 | margin: 0; 38 | background: linear-gradient(0deg, rgba(9,9,121,1) 0%, rgba(0,212,255,1) 100%); 39 | color: white; 40 | } 41 | 42 | footer { 43 | width: 100%; 44 | min-height: 120px; 45 | background: gray; 46 | color: white; 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: center; 50 | padding: 12px; 51 | margin-top: 64px; 52 | } 53 | 54 | h1 { 55 | font-size: 2.5em; 56 | font-weight: 400; 57 | } 58 | 59 | h2 { 60 | font-size: 1.6em; 61 | font-weight: 300; 62 | margin: 64px 0 12px; 63 | } 64 | 65 | h3 { 66 | font-weight: 300; 67 | } 68 | 69 | header h1 { 70 | width: auto; 71 | font-size: 2.8em; 72 | margin: 0; 73 | } 74 | 75 | header h2 { 76 | width: auto; 77 | margin: 0; 78 | } 79 | 80 | nav { 81 | display: grid; 82 | width: 100%; 83 | max-width: 100%; 84 | grid-template-columns: repeat(auto-fit, 240px); 85 | justify-content: center; 86 | border-bottom: 1px solid #efefef; 87 | } 88 | 89 | nav > a { 90 | color: #444; 91 | display: block; 92 | flex: 1; 93 | font-size: 18px; 94 | padding: 20px 0; 95 | text-align: center; 96 | text-decoration: none; 97 | } 98 | 99 | nav > a:hover { 100 | text-decoration: underline; 101 | } 102 | 103 | nav.collection { 104 | border: none; 105 | } 106 | 107 | nav.collection > ul { 108 | padding: 0; 109 | list-style: none; 110 | } 111 | 112 | nav.collection > ul > li { 113 | padding: 4px 0; 114 | } 115 | 116 | nav.collection > ul > li.selected { 117 | font-weight: 600; 118 | } 119 | 120 | nav.collection a { 121 | text-decoration: none; 122 | } 123 | 124 | nav.collection a:hover { 125 | text-decoration: underline; 126 | } 127 | 128 | section.columns { 129 | display: grid; 130 | grid-template-columns: repeat(auto-fit, minmax(400px, 488px)); 131 | grid-gap: 48px; 132 | justify-content: center; 133 | } 134 | 135 | section.columns > div { 136 | flex: 1; 137 | } 138 | 139 | section.examples { 140 | display: grid; 141 | grid-template-columns: 240px minmax(400px, 784px); 142 | grid-gap: 48px; 143 | justify-content: center; 144 | } 145 | 146 | section.examples h2:first-of-type { 147 | margin-top: 0; 148 | } 149 | 150 | table { 151 | width: 100%; 152 | border-collapse: collapse; 153 | } 154 | th { 155 | font-weight: 600; 156 | } 157 | 158 | td, th { 159 | border: solid 1px #aaa; 160 | padding: 4px; 161 | text-align: left; 162 | } 163 | -------------------------------------------------------------------------------- /docs/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <pdf-viewer> ⌲ Examples ⌲ Basic 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

<my-element>

19 |

A web component just for me.

20 |
21 | 22 | 28 |
29 |
30 | 31 |

Example: Basic

32 |
33 | 46 |
47 | 53 | 54 |

This is child content

55 |
56 |

CSS

57 |
  p {
border: solid 1px blue;
padding: 8px;
}
58 |

HTML

59 |
<pdf-viewer>
<p>This is child content</p>
</pdf-viewer>
60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 73 | 74 | -------------------------------------------------------------------------------- /docs/examples/name-property/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <pdf-viewer> ⌲ Examples ⌲ Name Property 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

<my-element>

19 |

A web component just for me.

20 |
21 | 22 | 28 |
29 |
30 | 31 |

Example: Name Property

32 |
33 | 46 |
47 |

48 |

HTML

49 |
<pdf-viewer name="Earth"></pdf-viewer>
50 | 51 |
52 |
53 | 54 |
55 |
56 | 57 | 63 | 64 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <pdf-viewer> ⌲ Home 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

<my-element>

19 |

A web component just for me.

20 |
21 | 22 | 28 |
29 |
30 |

<pdf-viewer>

31 |

<pdf-viewer> is an awesome element. It's a great introduction to building web components with LitElement, with nice documentation site as well.

32 |

As easy as HTML

33 |
34 |
35 |

<pdf-viewer> is just an HTML element. You can it anywhere you can use HTML!

36 |
<pdf-viewer></pdf-viewer>
37 |
38 |
39 |

40 |
41 |
42 |

Configure with attributes

43 |
44 |
45 |

<pdf-viewer> can be configured with attributed in plain HTML.

46 |
<pdf-viewer name="HTML"></pdf-viewer>
47 |
48 |
49 |

50 |
51 |
52 |

Declarative rendering

53 |
54 |
55 |

<pdf-viewer> can be used with declarative rendering libraries like Angular, React, Vue, and lit-html

56 |
import {html, render} from 'lit-html';

const name="lit-html";

render(html`
<h2>This is a &lt;pdf-viewer&gt;</h2>
<pdf-viewer .name=${name}></pdf-viewer>
`
, document.body);
57 |
58 |
59 |

This is a <pdf-viewer>

60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 73 | 74 | -------------------------------------------------------------------------------- /docs/install/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <pdf-viewer> ⌲ Install 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

<my-element>

19 |

A web component just for me.

20 |
21 | 22 | 28 |
29 |
30 |

Install

31 |

<pdf-viewer> is distributed on npm, so you can install it locally or use it via npm CDNs like unpkg.com.

32 |

Local Installation

33 |
npm i pdf-viewer
34 |

CDN

35 |

npm CDNs like unpkg.com can directly serve files that have been published to npm. This works great for standard JavaScript modules that the browser can load natively.

36 |

For this element to work from unpkg.com specifically, you need to include the ?module query parameter, which tells unpkg.com to rewrite "bare" module specificers to full URLs.

37 |

HTML

38 |
<script type="module" src="https://unpkg.com/pdf-viewer?module"></script>
39 |

JavaScript

40 |
import {MyElement} from 'https://unpkg.com/pdf-viewer?module';
41 | 42 |
43 |
44 | 45 | 51 | 52 | -------------------------------------------------------------------------------- /docs/prism-okaidia.css: -------------------------------------------------------------------------------- 1 | /** 2 | * okaidia theme for JavaScript, CSS and HTML 3 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 4 | * @author ocodia 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #f8f8f2; 10 | background: none; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .token.namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function, 100 | .token.class-name { 101 | color: #e6db74; 102 | } 103 | 104 | .token.keyword { 105 | color: #66d9ef; 106 | } 107 | 108 | .token.regex, 109 | .token.important { 110 | color: #fd971f; 111 | } 112 | 113 | .token.important, 114 | .token.bold { 115 | font-weight: bold; 116 | } 117 | .token.italic { 118 | font-style: italic; 119 | } 120 | 121 | .token.entity { 122 | cursor: help; 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdf-viewer-element", 3 | "version": "0.1.0-pre.4", 4 | "description": "A web component for displaying PDFs", 5 | "main": "index.js", 6 | "module": "index.js", 7 | "type": "module", 8 | "scripts": { 9 | "build": "tsc", 10 | "build:watch": "tsc --watch", 11 | "clean": "git clean -xffd -e node_modules/", 12 | "lint": "npm run lint:lit-analyzer && npm run lint:eslint", 13 | "lint:eslint": "eslint 'src/**/*.ts'", 14 | "lint:lit-analyzer": "lit-analyzer", 15 | "format": "prettier src/* --write", 16 | "docs": "npm run docs:clean && npm run build && npm run analyze && npm run docs:build && npm run docs:assets && npm run docs:gen", 17 | "docs:clean": "rimraf docs", 18 | "docs:gen": "eleventy --config=.eleventy.cjs", 19 | "docs:gen:watch": "eleventy --config=.eleventy.cjs --watch", 20 | "docs:build": "rollup -c --file docs/pdf-viewer.bundled.js", 21 | "docs:assets": "cp node_modules/prismjs/themes/prism-okaidia.css docs/", 22 | "docs:serve": "es-dev-server --root-dir=docs --node-resolve --watch", 23 | "analyze": "wca analyze \"src/**/*.ts\" --outFile custom-elements.json", 24 | "serve": "web-dev-server --node-resolve --watch --babel-exclude=**/@bundled-es-modules/pdfjs-dist/build/*", 25 | "test": "echo \"No tests yet\"" 26 | }, 27 | "keywords": [ 28 | "pdf", 29 | "custom element", 30 | "web component", 31 | "lit" 32 | ], 33 | "author": "Justin Fagnani ", 34 | "license": "Apache-2.0", 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/justinfagnani/pdf-viewer-element.git" 38 | }, 39 | "dependencies": { 40 | "@bundled-es-modules/pdfjs-dist": "^2.5.207-rc1", 41 | "@lit-labs/context": "^0.1.3", 42 | "@material/mwc-fab": "^0.27.0", 43 | "@material/mwc-icon-button": "^0.27.0", 44 | "lit": "^2.3.1" 45 | }, 46 | "devDependencies": { 47 | "@11ty/eleventy": "^0.9.0", 48 | "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3", 49 | "@material/mwc-formfield": "^0.27.0", 50 | "@material/mwc-switch": "^0.27.0", 51 | "@material/mwc-textfield": "^0.27.0", 52 | "@material/mwc-top-app-bar-fixed": "^0.27.0", 53 | "@types/pdfjs-dist": "^2.1.5", 54 | "@web/dev-server": "^0.1.34", 55 | "lit-analyzer": "^1.2.1", 56 | "prettier": "^2.7.1", 57 | "rollup-plugin-node-resolve": "^5.2.0", 58 | "typescript": "~4.8.2", 59 | "web-component-analyzer": "^1.1.6" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/pdf-viewer-display.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Justin Fagnani 3 | */ 4 | import {LitElement, html, css, PropertyValues, CSSResultGroup} from 'lit'; 5 | import {property, customElement, query} from 'lit/decorators.js'; 6 | 7 | import pdfjs from '@bundled-es-modules/pdfjs-dist'; 8 | import viewer from '@bundled-es-modules/pdfjs-dist/web/pdf_viewer'; 9 | import {styles} from '../lib/styles.js'; 10 | 11 | pdfjs.GlobalWorkerOptions.workerSrc = 12 | '/node_modules/@bundled-es-modules/pdfjs-dist/build/pdf.worker.min.js'; 13 | 14 | const ptToPx: number = 96.0 / 72.0; 15 | 16 | /** 17 | * A web component that displays PDFs 18 | * 19 | * @cssprop [--pdf-viewer-top-bar-height=48px] 20 | * @cssprop [--pdf-viewer-page-shadow=2px 2px 2px 1px rgba(0, 0, 0, 0.2)] 21 | * @cssprop [--pdf-viewer-background=gray] 22 | */ 23 | @customElement('pdf-viewer-display') 24 | export class PDFViewerDisplayElement extends LitElement { 25 | static styles = [ 26 | styles, 27 | css` 28 | :host { 29 | display: block; 30 | position: relative; 31 | height: 480px; 32 | --pdf-viewer-background: gray; 33 | --pdf-viewer-page-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2); 34 | background: --pdf-viewer-background; 35 | } 36 | #container { 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | bottom: 0; 41 | right: 0; 42 | overflow: auto; 43 | } 44 | /* 45 | Styling .canvasWrapper because .page has a padding and the drop-shadow 46 | is offset from the page. 47 | */ 48 | .canvasWrapper { 49 | box-shadow: var(--pdf-viewer-page-shadow); 50 | } 51 | `, 52 | ] as CSSResultGroup; 53 | 54 | @property({type: String, reflect: true}) 55 | src?: string; 56 | 57 | /** 58 | * The current 1-based page number. 59 | */ 60 | @property({type: Number, reflect: true}) 61 | page: number = 1; 62 | 63 | /** 64 | * Total page count of the current document. 65 | */ 66 | get pageCount() { 67 | return this._pdfViewer?.pagesCount; 68 | } 69 | 70 | /** 71 | * Whether multiple pages should render. Single page rendering is much faster. 72 | */ 73 | @property({ 74 | attribute: 'multi-page', 75 | type: Boolean, 76 | reflect: true, 77 | }) 78 | multiPage = false; 79 | 80 | @property({ 81 | reflect: true, 82 | }) 83 | scale: number | 'cover' | 'contain' = 'cover'; 84 | 85 | private _currentScale?: number; 86 | 87 | @property({type: Number, reflect: true}) 88 | zoom = 1; 89 | 90 | @property({attribute: 'document-title'}) 91 | documentTitle?: string; 92 | 93 | // TODO: This is the border on div.page make by pdf.js. Where does it come 94 | // from, and can we read or set it? 95 | private _pageBorderWidth = 9; 96 | 97 | // TODO: This is the macOS border size, used to reserve space for scrollbars 98 | // and prevent an overflow on one axis from unneccessarily causing an overflow 99 | // on the other axis. There's got to be a better way. 100 | private _scrollBarSize = 16; 101 | 102 | @query('#viewer') 103 | private _viewerElement!: HTMLDivElement; 104 | 105 | private _pdfViewer?: typeof viewer.PDFViewer; 106 | 107 | private _pdfDocument?: any; 108 | 109 | private _resizeObserver: ResizeObserver = new ResizeObserver(() => 110 | this._onResize() 111 | ); 112 | 113 | private _eventBus = new viewer.EventBus(); 114 | 115 | constructor() { 116 | super(); 117 | this._resizeObserver.observe(this); 118 | } 119 | 120 | render() { 121 | return html`
`; 122 | } 123 | 124 | async updated(changedProperties: PropertyValues) { 125 | let setScale = false; 126 | if (changedProperties.has('multiPage')) { 127 | setScale = true; 128 | const container = this.shadowRoot!.querySelector( 129 | '#container' 130 | ) as HTMLElement; 131 | // When multiPage changes we must make a new viewer element. 132 | container.innerHTML = '
'; 133 | if (this.multiPage) { 134 | this._pdfViewer = new viewer.PDFViewer({ 135 | container, 136 | eventBus: this._eventBus, 137 | viewer: this._viewerElement, 138 | // linkService: pdfLinkService, 139 | // findController: pdfFindController, 140 | }); 141 | } else { 142 | this._pdfViewer = new viewer.PDFSinglePageViewer({ 143 | container, 144 | eventBus: this._eventBus, 145 | // viewer: this._viewerElement, 146 | // linkService: pdfLinkService, 147 | // findController: pdfFindController, 148 | }); 149 | } 150 | if (this._pdfDocument) { 151 | this._pdfViewer.setDocument(this._pdfDocument); 152 | } 153 | this.requestUpdate(); 154 | } 155 | 156 | if (changedProperties.has('src')) { 157 | this._load(); 158 | } 159 | 160 | if (changedProperties.has('page')) { 161 | this._pdfViewer.scrollPageIntoView({ 162 | pageNumber: this.page, 163 | }); 164 | } 165 | 166 | if (this._pdfDocument !== undefined) { 167 | if (this._currentScale === undefined || changedProperties.has('scale')) { 168 | setScale = true; 169 | if (this.scale === 'cover' || this.scale === 'contain') { 170 | const page = await this._pdfDocument.getPage( 171 | this._pdfViewer.currentPageNumber 172 | ); 173 | const viewport = page.getViewport({ 174 | scale: 1, 175 | rotation: 0, 176 | }); 177 | const availableWidth = 178 | this.offsetWidth - this._pageBorderWidth * 2 - this._scrollBarSize; 179 | const availableHeight = 180 | this.offsetHeight - this._pageBorderWidth * 2 - this._scrollBarSize; 181 | const viewportWidthPx = viewport.width * ptToPx; 182 | const viewportHeightPx = viewport.height * ptToPx; 183 | const fitWidthScale = availableWidth / viewportWidthPx; 184 | const fitHeightScale = availableHeight / viewportHeightPx; 185 | if (this.scale === 'cover') { 186 | this._currentScale = Math.max(fitWidthScale, fitHeightScale); 187 | } else { 188 | this._currentScale = Math.min(fitWidthScale, fitHeightScale); 189 | } 190 | } else { 191 | this._currentScale = this.scale; 192 | } 193 | } 194 | if (setScale) { 195 | // TODO: if the viewer is new we have to wait for "pagesinit"? 196 | this._pdfViewer.currentScale = this._currentScale * this.zoom; 197 | } 198 | } 199 | } 200 | 201 | private async _load() { 202 | try { 203 | const loadingTask = pdfjs.getDocument({ 204 | url: this.src, 205 | // cMapUrl: CMAP_URL, 206 | // cMapPacked: CMAP_PACKED, 207 | }); 208 | const pdfDocument = await loadingTask.promise; 209 | if (this._pdfDocument) { 210 | this._pdfDocument.destroy(); 211 | } 212 | this._pdfDocument = pdfDocument; 213 | // Document loaded, specifying document for the viewer and 214 | // the (optional) linkService. 215 | this._pdfViewer.setDocument(pdfDocument); 216 | // pdfLinkService.setDocument(pdfDocument, null); 217 | const metadata = await pdfDocument.getMetadata(); 218 | console.log({metadata}); 219 | this.documentTitle = (metadata.info as any).Title; 220 | await this.requestUpdate(); 221 | this.dispatchEvent(new Event('load')); 222 | } catch (e) { 223 | this.dispatchEvent( 224 | new ErrorEvent('error', { 225 | error: e, 226 | }) 227 | ); 228 | } 229 | } 230 | 231 | _onResize() { 232 | console.log('_onResize'); 233 | this.requestUpdate(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/components/pdf-viewer-toolbar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Justin Fagnani 3 | */ 4 | import {LitElement, html, css, PropertyValues} from 'lit'; 5 | import {customElement, state} from 'lit/decorators.js'; 6 | import '@material/mwc-icon-button'; 7 | import {contextProvided} from '@lit-labs/context'; 8 | import type {PDFViewerElement} from './pdf-viewer.js'; 9 | import {viewerContext} from './viewer-context.js'; 10 | 11 | @customElement('pdf-viewer-toolbar') 12 | export class PDFViewerToolbarElement extends LitElement { 13 | static styles = css` 14 | :host { 15 | top: 0; 16 | height: var(--pdf-viewer-top-bar-height); 17 | display: flex; 18 | flex-direction: row; 19 | justify-content: center; 20 | align-items: center; 21 | color: var(--mdc-theme-on-primary, white); 22 | background: var(--mdc-theme-primary, #37474f); 23 | /* From https://github.com/material-components/material-components-web/blob/8d8f3fcb3f2940b071f761497490c1b0123e106b/packages/mdc-typography/_variables.scss */ 24 | font-family: Roboto, sans-serif; 25 | font-size: 1rem; 26 | line-height: 2rem; 27 | font-weight: medium; 28 | letter-spacing: 1.25; 29 | /* 10px is the same as the content margin */ 30 | padding: 0 10px; 31 | } 32 | :host > * { 33 | flex: 1; 34 | } 35 | #title { 36 | text-align: center; 37 | } 38 | #page-number { 39 | text-align: center; 40 | vertical-align: middle; 41 | } 42 | #nav { 43 | text-align: right; 44 | --mdc-icon-button-size: 36px; 45 | } 46 | `; 47 | 48 | @contextProvided({context: viewerContext}) 49 | @state() 50 | _viewer: PDFViewerElement | undefined; 51 | 52 | // _viewerContext = new ContextConsumer(this, viewerContext, (viewer) => { 53 | 54 | // }); 55 | 56 | willUpdate(changedProperties: PropertyValues) { 57 | if (changedProperties.has('_viewer')) { 58 | const oldViewer = changedProperties.get('_viewer'); 59 | if (oldViewer !== undefined) { 60 | oldViewer.removeEventListener('change', this._onViewerChange); 61 | } 62 | if (this._viewer !== undefined) { 63 | this._viewer.addEventListener('change', this._onViewerChange); 64 | } 65 | } 66 | } 67 | 68 | private _onViewerChange = () => { 69 | this.requestUpdate(); 70 | }; 71 | 72 | render() { 73 | return html` 74 | 75 | 79 | 80 | ${this._viewer?.documentTitle} 81 | 82 | 88 | 89 | ${this._viewer?.page} / ${this._viewer?.pageCount} 92 | 98 | 99 | 100 | `; 101 | } 102 | 103 | private _previousPage() { 104 | this._viewer?.previousPage(); 105 | } 106 | 107 | private _nextPage() { 108 | this._viewer?.nextPage(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/components/pdf-viewer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Justin Fagnani 3 | */ 4 | import {LitElement, html, css, PropertyValues} from 'lit'; 5 | import {property, customElement, query} from 'lit/decorators.js'; 6 | import '@material/mwc-fab'; 7 | import '@material/mwc-icon-button'; 8 | import './pdf-viewer-display.js'; 9 | import {PDFViewerDisplayElement} from './pdf-viewer-display.js'; 10 | import './pdf-viewer-toolbar.js'; 11 | import {ContextProvider} from '@lit-labs/context'; 12 | import {viewerContext} from './viewer-context.js'; 13 | 14 | /** 15 | * A web component that displays PDFs 16 | * 17 | * @cssprop [--pdf-viewer-top-bar-height=48px] 18 | * @cssprop [--pdf-viewer-page-shadow=2px 2px 2px 1px rgba(0, 0, 0, 0.2)] 19 | * @cssprop [--pdf-viewer-background=gray] 20 | */ 21 | @customElement('pdf-viewer') 22 | export class PDFViewerElement extends LitElement { 23 | static styles = css` 24 | :host { 25 | display: flex; 26 | flex-direction: column; 27 | position: relative; 28 | --mdc-theme-primary: var(--mdc-theme-primary, #37474f); 29 | --mdc-theme-on-primary: var(--mdc-theme-on-primary, white); 30 | --mdc-theme-secondary: white; 31 | --mdc-theme-on-secondary: black; 32 | --pdf-viewer-top-bar-height: 48px; 33 | --pdf-viewer-page-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2); 34 | } 35 | pdf-viewer-display { 36 | width: 100%; 37 | height: 100%; 38 | } 39 | #controls { 40 | display: block; 41 | position: absolute; 42 | top: 0; 43 | left: 0; 44 | width: 100%; 45 | height: 100%; 46 | user-select: none; 47 | pointer-events: none; 48 | } 49 | #controls > * { 50 | user-select: initial; 51 | pointer-events: initial; 52 | } 53 | #zoom-out { 54 | position: absolute; 55 | bottom: 50px; 56 | right: 40px; 57 | } 58 | #zoom-in { 59 | position: absolute; 60 | bottom: 100px; 61 | right: 40px; 62 | } 63 | `; 64 | 65 | @query('pdf-viewer-display') 66 | private _display?: PDFViewerDisplayElement; 67 | 68 | @property({type: String, reflect: true}) 69 | src?: string; 70 | 71 | /** 72 | * The current 1-based page number. 73 | */ 74 | @property({type: Number, reflect: true}) 75 | page: number = 1; 76 | 77 | /** 78 | * Whether multiple pages should render. Single page rendering is much faster. 79 | */ 80 | @property({ 81 | attribute: 'multi-page', 82 | type: Boolean, 83 | reflect: true, 84 | }) 85 | multiPage = false; 86 | 87 | @property({ 88 | reflect: true, 89 | }) 90 | scale: number | 'cover' | 'contain' = 'cover'; 91 | 92 | @property({type: Number, reflect: true}) 93 | zoom: number = 1; 94 | 95 | @property({type: Boolean, reflect: true, attribute: 'hide-toolbar'}) 96 | hideToolbar = false; 97 | 98 | /** 99 | * Total page count of the current document. 100 | */ 101 | get pageCount() { 102 | return this._display?.pageCount ?? 0; 103 | } 104 | 105 | get documentTitle() { 106 | return this._display?.documentTitle ?? ''; 107 | } 108 | 109 | constructor() { 110 | super(); 111 | new ContextProvider(this, viewerContext, this); 112 | } 113 | 114 | render() { 115 | return html` 118 | 127 |
128 | 129 | 130 | 131 |
`; 132 | } 133 | 134 | updated(changedProperties: PropertyValues) { 135 | if (changedProperties.has('page')) { 136 | this.dispatchEvent(new Event('change')); 137 | } 138 | } 139 | 140 | _onLoad() { 141 | console.log('_onLoad'); 142 | this.requestUpdate(); 143 | this.dispatchEvent(new Event('change')); 144 | } 145 | 146 | zoomOut() { 147 | this.zoom = Math.max(this.zoom - 0.25, 0.25); 148 | } 149 | 150 | zoomIn() { 151 | // TODO: what's the max zoom? 152 | this.zoom += 0.25; 153 | } 154 | 155 | nextPage() { 156 | if (this.page < this.pageCount) { 157 | this.page++; 158 | } 159 | } 160 | 161 | previousPage() { 162 | if (this.page > 1) { 163 | this.page--; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/components/viewer-context.ts: -------------------------------------------------------------------------------- 1 | import {createContext} from '@lit-labs/context'; 2 | import type {PDFViewerElement} from './pdf-viewer.js'; 3 | 4 | export const viewerContext = createContext( 5 | Symbol('pdf-viewer') 6 | ); 7 | -------------------------------------------------------------------------------- /src/demo/pdf-viewer-demo.ts: -------------------------------------------------------------------------------- 1 | import {LitElement, html, css} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {TextField} from '@material/mwc-textfield'; 4 | import '@material/mwc-textfield'; 5 | import {TopAppBarFixed} from '@material/mwc-top-app-bar-fixed'; 6 | import '../components/pdf-viewer.js'; 7 | 8 | import '@material/mwc-switch'; 9 | import type {Switch} from '@material/mwc-switch'; 10 | import '@material/mwc-formfield'; 11 | 12 | @customElement('pdf-viewer-app-bar') 13 | export class TopAppBar extends TopAppBarFixed { 14 | static styles = [ 15 | TopAppBarFixed.styles, 16 | css` 17 | .mdc-top-app-bar--fixed-adjust { 18 | height: 100%; 19 | box-sizing: border-box; 20 | } 21 | `, 22 | ] as any; 23 | } 24 | 25 | @customElement('pdf-viewer-demo') 26 | export class PDFViewerDemo extends LitElement { 27 | @property() src: string = './f1040.pdf'; 28 | @property({attribute: 'multi-page', type: Boolean}) multiPage = false; 29 | @property({type: Number}) page = 1; 30 | @property({type: Boolean}) hideToolbar = false; 31 | 32 | static styles = css` 33 | pdf-viewer-app-bar { 34 | height: 100vh; 35 | background: #efefef; 36 | } 37 | #content { 38 | display: flex; 39 | flex-direction: row; 40 | height: 100%; 41 | } 42 | #controls { 43 | background: white; 44 | display: flex; 45 | gap: 8px; 46 | flex-direction: column; 47 | align-items: baseline; 48 | padding: 16px; 49 | } 50 | #demo-container { 51 | flex: auto; 52 | display: flex; 53 | align-items: center; 54 | justify-content: center; 55 | } 56 | pdf-viewer { 57 | width: 800px; 58 | height: 800px; 59 | } 60 | `; 61 | 62 | render() { 63 | console.log('demo render', this.multiPage); 64 | return html` 65 | 66 |
<pdf-viewer> Demo
67 |
68 |
69 | 76 | 77 | 81 | 82 | 89 | 90 | 94 | 95 |
96 |
97 | 103 |
104 |
105 |
106 | `; 107 | } 108 | 109 | _srcChanged(e: Event) { 110 | this.src = (e.target as TextField).value; 111 | } 112 | 113 | _pageChanged(e: Event) { 114 | this.page = Number((e.target as TextField).value); 115 | } 116 | 117 | _multiPageChanged(e: Event) { 118 | this.multiPage = (e.target as Switch).selected; 119 | } 120 | 121 | _hideToolbarChanged(e: Event) { 122 | console.log('_hideToolbarChanged', (e.target as Switch).selected); 123 | this.hideToolbar = (e.target as Switch).selected; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import './components/pdf-viewer.js'; 2 | export {PDFViewerElement} from './components/pdf-viewer.js'; 3 | -------------------------------------------------------------------------------- /src/lib/styles.ts: -------------------------------------------------------------------------------- 1 | import {css} from 'lit'; 2 | 3 | export const styles = css` 4 | /* Copyright 2014 Mozilla Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | .textLayer { 20 | position: absolute; 21 | left: 0; 22 | top: 0; 23 | right: 0; 24 | bottom: 0; 25 | overflow: hidden; 26 | opacity: 0.2; 27 | line-height: 1; 28 | } 29 | 30 | .textLayer > span { 31 | color: transparent; 32 | position: absolute; 33 | white-space: pre; 34 | cursor: text; 35 | -webkit-transform-origin: 0% 0%; 36 | transform-origin: 0% 0%; 37 | } 38 | 39 | .textLayer .highlight { 40 | margin: -1px; 41 | padding: 1px; 42 | 43 | background-color: rgb(180, 0, 170); 44 | border-radius: 4px; 45 | } 46 | 47 | .textLayer .highlight.begin { 48 | border-radius: 4px 0px 0px 4px; 49 | } 50 | 51 | .textLayer .highlight.end { 52 | border-radius: 0px 4px 4px 0px; 53 | } 54 | 55 | .textLayer .highlight.middle { 56 | border-radius: 0px; 57 | } 58 | 59 | .textLayer .highlight.selected { 60 | background-color: rgb(0, 100, 0); 61 | } 62 | 63 | .textLayer ::-moz-selection { 64 | background: rgb(0, 0, 255); 65 | } 66 | 67 | .textLayer ::selection { 68 | background: rgb(0, 0, 255); 69 | } 70 | 71 | .textLayer .endOfContent { 72 | display: block; 73 | position: absolute; 74 | left: 0px; 75 | top: 100%; 76 | right: 0px; 77 | bottom: 0px; 78 | z-index: -1; 79 | cursor: default; 80 | -webkit-user-select: none; 81 | -moz-user-select: none; 82 | -ms-user-select: none; 83 | user-select: none; 84 | } 85 | 86 | .textLayer .endOfContent.active { 87 | top: 0px; 88 | } 89 | 90 | .annotationLayer section { 91 | position: absolute; 92 | } 93 | 94 | .annotationLayer .linkAnnotation > a, 95 | .annotationLayer .buttonWidgetAnnotation.pushButton > a { 96 | position: absolute; 97 | font-size: 1em; 98 | top: 0; 99 | left: 0; 100 | width: 100%; 101 | height: 100%; 102 | } 103 | 104 | .annotationLayer .linkAnnotation > a:hover, 105 | .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { 106 | opacity: 0.2; 107 | background: #ff0; 108 | box-shadow: 0px 2px 10px #ff0; 109 | } 110 | 111 | .annotationLayer .textAnnotation img { 112 | position: absolute; 113 | cursor: pointer; 114 | } 115 | 116 | .annotationLayer .textWidgetAnnotation input, 117 | .annotationLayer .textWidgetAnnotation textarea, 118 | .annotationLayer .choiceWidgetAnnotation select, 119 | .annotationLayer .buttonWidgetAnnotation.checkBox input, 120 | .annotationLayer .buttonWidgetAnnotation.radioButton input { 121 | background-color: rgba(0, 54, 255, 0.13); 122 | border: 1px solid transparent; 123 | box-sizing: border-box; 124 | font-size: 9px; 125 | height: 100%; 126 | margin: 0; 127 | padding: 0 3px; 128 | vertical-align: top; 129 | width: 100%; 130 | } 131 | 132 | .annotationLayer .choiceWidgetAnnotation select option { 133 | padding: 0; 134 | } 135 | 136 | .annotationLayer .buttonWidgetAnnotation.radioButton input { 137 | border-radius: 50%; 138 | } 139 | 140 | .annotationLayer .textWidgetAnnotation textarea { 141 | font: message-box; 142 | font-size: 9px; 143 | resize: none; 144 | } 145 | 146 | .annotationLayer .textWidgetAnnotation input[disabled], 147 | .annotationLayer .textWidgetAnnotation textarea[disabled], 148 | .annotationLayer .choiceWidgetAnnotation select[disabled], 149 | .annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], 150 | .annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { 151 | background: none; 152 | border: 1px solid transparent; 153 | cursor: not-allowed; 154 | } 155 | 156 | .annotationLayer .textWidgetAnnotation input:hover, 157 | .annotationLayer .textWidgetAnnotation textarea:hover, 158 | .annotationLayer .choiceWidgetAnnotation select:hover, 159 | .annotationLayer .buttonWidgetAnnotation.checkBox input:hover, 160 | .annotationLayer .buttonWidgetAnnotation.radioButton input:hover { 161 | border: 1px solid #000; 162 | } 163 | 164 | .annotationLayer .textWidgetAnnotation input:focus, 165 | .annotationLayer .textWidgetAnnotation textarea:focus, 166 | .annotationLayer .choiceWidgetAnnotation select:focus { 167 | background: none; 168 | border: 1px solid transparent; 169 | } 170 | 171 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, 172 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after, 173 | .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { 174 | background-color: #000; 175 | content: ''; 176 | display: block; 177 | position: absolute; 178 | } 179 | 180 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, 181 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { 182 | height: 80%; 183 | left: 45%; 184 | width: 1px; 185 | } 186 | 187 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before { 188 | -webkit-transform: rotate(45deg); 189 | transform: rotate(45deg); 190 | } 191 | 192 | .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { 193 | -webkit-transform: rotate(-45deg); 194 | transform: rotate(-45deg); 195 | } 196 | 197 | .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { 198 | border-radius: 50%; 199 | height: 50%; 200 | left: 30%; 201 | top: 20%; 202 | width: 50%; 203 | } 204 | 205 | .annotationLayer .textWidgetAnnotation input.comb { 206 | font-family: monospace; 207 | padding-left: 2px; 208 | padding-right: 0; 209 | } 210 | 211 | .annotationLayer .textWidgetAnnotation input.comb:focus { 212 | /* 213 | * Letter spacing is placed on the right side of each character. Hence, the 214 | * letter spacing of the last character may be placed outside the visible 215 | * area, causing horizontal scrolling. We avoid this by extending the width 216 | * when the element has focus and revert this when it loses focus. 217 | */ 218 | width: 115%; 219 | } 220 | 221 | .annotationLayer .buttonWidgetAnnotation.checkBox input, 222 | .annotationLayer .buttonWidgetAnnotation.radioButton input { 223 | -webkit-appearance: none; 224 | -moz-appearance: none; 225 | appearance: none; 226 | padding: 0; 227 | } 228 | 229 | .annotationLayer .popupWrapper { 230 | position: absolute; 231 | width: 20em; 232 | } 233 | 234 | .annotationLayer .popup { 235 | position: absolute; 236 | z-index: 200; 237 | max-width: 20em; 238 | background-color: #ffff99; 239 | box-shadow: 0px 2px 5px #888; 240 | border-radius: 2px; 241 | padding: 6px; 242 | margin-left: 5px; 243 | cursor: pointer; 244 | font: message-box; 245 | font-size: 9px; 246 | word-wrap: break-word; 247 | } 248 | 249 | .annotationLayer .popup > * { 250 | font-size: 9px; 251 | } 252 | 253 | .annotationLayer .popup h1 { 254 | display: inline-block; 255 | } 256 | 257 | .annotationLayer .popup span { 258 | display: inline-block; 259 | margin-left: 5px; 260 | } 261 | 262 | .annotationLayer .popup p { 263 | border-top: 1px solid #333; 264 | margin-top: 2px; 265 | padding-top: 2px; 266 | } 267 | 268 | .annotationLayer .highlightAnnotation, 269 | .annotationLayer .underlineAnnotation, 270 | .annotationLayer .squigglyAnnotation, 271 | .annotationLayer .strikeoutAnnotation, 272 | .annotationLayer .freeTextAnnotation, 273 | .annotationLayer .lineAnnotation svg line, 274 | .annotationLayer .squareAnnotation svg rect, 275 | .annotationLayer .circleAnnotation svg ellipse, 276 | .annotationLayer .polylineAnnotation svg polyline, 277 | .annotationLayer .polygonAnnotation svg polygon, 278 | .annotationLayer .caretAnnotation, 279 | .annotationLayer .inkAnnotation svg polyline, 280 | .annotationLayer .stampAnnotation, 281 | .annotationLayer .fileAttachmentAnnotation { 282 | cursor: pointer; 283 | } 284 | 285 | .pdfViewer .canvasWrapper { 286 | overflow: hidden; 287 | } 288 | 289 | .pdfViewer .page { 290 | direction: ltr; 291 | width: 816px; 292 | height: 1056px; 293 | margin: 1px auto -8px auto; 294 | position: relative; 295 | overflow: visible; 296 | border: 9px solid transparent; 297 | background-clip: content-box; 298 | -webkit-border-image: url(images/shadow.png) 9 9 repeat; 299 | -o-border-image: url(images/shadow.png) 9 9 repeat; 300 | border-image: url(images/shadow.png) 9 9 repeat; 301 | background-color: white; 302 | } 303 | 304 | .pdfViewer.removePageBorders .page { 305 | margin: 0px auto 10px auto; 306 | border: none; 307 | } 308 | 309 | .pdfViewer.singlePageView { 310 | display: inline-block; 311 | } 312 | 313 | .pdfViewer.singlePageView .page { 314 | margin: 0; 315 | border: none; 316 | } 317 | 318 | .pdfViewer.scrollHorizontal, 319 | .pdfViewer.scrollWrapped, 320 | .spread { 321 | margin-left: 3.5px; 322 | margin-right: 3.5px; 323 | text-align: center; 324 | } 325 | 326 | .pdfViewer.scrollHorizontal, 327 | .spread { 328 | white-space: nowrap; 329 | } 330 | 331 | .pdfViewer.removePageBorders, 332 | .pdfViewer.scrollHorizontal .spread, 333 | .pdfViewer.scrollWrapped .spread { 334 | margin-left: 0; 335 | margin-right: 0; 336 | } 337 | 338 | .spread .page, 339 | .pdfViewer.scrollHorizontal .page, 340 | .pdfViewer.scrollWrapped .page, 341 | .pdfViewer.scrollHorizontal .spread, 342 | .pdfViewer.scrollWrapped .spread { 343 | display: inline-block; 344 | vertical-align: middle; 345 | } 346 | 347 | .spread .page, 348 | .pdfViewer.scrollHorizontal .page, 349 | .pdfViewer.scrollWrapped .page { 350 | margin-left: -3.5px; 351 | margin-right: -3.5px; 352 | } 353 | 354 | .pdfViewer.removePageBorders .spread .page, 355 | .pdfViewer.removePageBorders.scrollHorizontal .page, 356 | .pdfViewer.removePageBorders.scrollWrapped .page { 357 | margin-left: 5px; 358 | margin-right: 5px; 359 | } 360 | 361 | .pdfViewer .page canvas { 362 | margin: 0; 363 | display: block; 364 | } 365 | 366 | .pdfViewer .page canvas[hidden] { 367 | display: none; 368 | } 369 | 370 | .pdfViewer .page .loadingIcon { 371 | position: absolute; 372 | display: block; 373 | left: 0; 374 | top: 0; 375 | right: 0; 376 | bottom: 0; 377 | background: url('images/loading-icon.gif') center no-repeat; 378 | } 379 | 380 | .pdfPresentationMode .pdfViewer { 381 | margin-left: 0; 382 | margin-right: 0; 383 | } 384 | 385 | .pdfPresentationMode .pdfViewer .page, 386 | .pdfPresentationMode .pdfViewer .spread { 387 | display: block; 388 | } 389 | 390 | .pdfPresentationMode .pdfViewer .page, 391 | .pdfPresentationMode .pdfViewer.removePageBorders .page { 392 | margin-left: auto; 393 | margin-right: auto; 394 | } 395 | 396 | .pdfPresentationMode:-ms-fullscreen .pdfViewer .page { 397 | margin-bottom: 100% !important; 398 | } 399 | 400 | .pdfPresentationMode:-webkit-full-screen .pdfViewer .page { 401 | margin-bottom: 100%; 402 | border: 0; 403 | } 404 | 405 | .pdfPresentationMode:-moz-full-screen .pdfViewer .page { 406 | margin-bottom: 100%; 407 | border: 0; 408 | } 409 | 410 | .pdfPresentationMode:fullscreen .pdfViewer .page { 411 | margin-bottom: 100%; 412 | border: 0; 413 | } 414 | `; 415 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "lib": ["es2022", "DOM", "DOM.Iterable"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "outDir": "./", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitThis": true, 18 | "moduleResolution": "node", 19 | "experimentalDecorators": true, 20 | "esModuleInterop": true, 21 | "skipLibCheck": true, 22 | }, 23 | "include": [ 24 | "src/**/*.ts", 25 | "custom_typings/*.d.ts" 26 | ], 27 | "exclude": [] 28 | } 29 | --------------------------------------------------------------------------------