fork from https://github.com/mozilla/pdf.js.git
20
.editorconfig
Normal file
@@ -0,0 +1,20 @@
|
||||
root = true
|
||||
|
||||
[*.{js,jsm,mjs,json,html,css,pdf.link}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,pdf.link}]
|
||||
max_line_length = off
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = off
|
||||
6
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,6 @@
|
||||
# Auto-format `.js` files with ESLint/Prettier
|
||||
de36b2aabab2b7fd647d9591f959c4540129541d
|
||||
# Auto-format `.css` files with Stylelint/Prettier
|
||||
8aa2718d225ad701a5b8a2788b42d221f1e4327d
|
||||
# Auto-format `.json` files with Prettier
|
||||
29de9bdce6c9785574994fda0e51533d796a9bb4
|
||||
23
.gitattributes
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Force Unix line endings for most file formats (except binary files)
|
||||
*.js text eol=lf
|
||||
*.jsm text eol=lf
|
||||
*.css text eol=lf
|
||||
*.html text eol=lf
|
||||
*.md text eol=lf
|
||||
*.ftl text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.json text eol=lf
|
||||
*.config text eol=lf
|
||||
*.inc text eol=lf
|
||||
*.manifest text eol=lf
|
||||
*.rdf text eol=lf
|
||||
*.jade text eol=lf
|
||||
*.coffee text eol=lf
|
||||
|
||||
# PDF files shall not modify CRLF line endings
|
||||
*.pdf -crlf
|
||||
|
||||
# Linguist language overrides
|
||||
*.js linguist-language=JavaScript
|
||||
*.jsm linguist-language=JavaScript
|
||||
*.inc linguist-language=XML
|
||||
24
.gitignore
vendored
@@ -1,14 +1,10 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
*~
|
||||
local.mk
|
||||
build/
|
||||
tags
|
||||
.DS_Store
|
||||
Makefile
|
||||
node_modules/
|
||||
examples/node/svgdump/
|
||||
examples/node/pdf2png/*.png
|
||||
.vscode/
|
||||
|
||||
9
.mcp.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"firefox-devtools": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["@padenot/firefox-devtools-mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
18
.prettierignore
Normal file
@@ -0,0 +1,18 @@
|
||||
build/
|
||||
l10n/
|
||||
docs/
|
||||
node_modules/
|
||||
external/bcmaps/
|
||||
external/builder/fixtures/
|
||||
external/builder/fixtures_babel/
|
||||
external/openjpeg/
|
||||
external/qcms/
|
||||
external/quickjs/
|
||||
test/stats/results/
|
||||
test/tmp/
|
||||
test/pdfs/
|
||||
web/locale/
|
||||
*~/
|
||||
.claude/
|
||||
.codex/
|
||||
.cursor/
|
||||
25
.prettierrc
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["tsconfig.json", ".prettierrc"],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.html"],
|
||||
"options": {
|
||||
"parser": "html",
|
||||
"printWidth": 160
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
9
.puppeteerrc.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"chrome": {
|
||||
"skipDownload": false
|
||||
},
|
||||
"firefox": {
|
||||
"skipDownload": false,
|
||||
"version": "nightly"
|
||||
}
|
||||
}
|
||||
13
.stylelintignore
Normal file
@@ -0,0 +1,13 @@
|
||||
build/
|
||||
l10n/
|
||||
docs/
|
||||
node_modules/
|
||||
external/bcmaps/
|
||||
external/builder/fixtures/
|
||||
external/builder/fixtures_babel/
|
||||
external/quickjs/
|
||||
test/stats/results/
|
||||
test/tmp/
|
||||
test/pdfs/
|
||||
web/locale/
|
||||
*~/
|
||||
22
.stylelintrc
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"plugins": [
|
||||
"stylelint-prettier"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"prettier/prettier": true,
|
||||
|
||||
"alpha-value-notation": "number",
|
||||
"block-no-empty": true,
|
||||
"color-function-notation": "modern",
|
||||
"color-hex-length": "short",
|
||||
"color-no-invalid-hex": true,
|
||||
"declaration-block-no-duplicate-properties": true,
|
||||
"declaration-block-no-redundant-longhand-properties": true,
|
||||
"length-zero-no-unit": [true, {
|
||||
"ignore": ["custom-properties"]
|
||||
}],
|
||||
"selector-pseudo-element-colon-notation": "double",
|
||||
"shorthand-property-no-redundant-values": true
|
||||
}
|
||||
}
|
||||
35
.svglintrc.js
Normal file
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
rules: {
|
||||
valid: true,
|
||||
|
||||
custom: [
|
||||
(reporter, $, ast, { filename }) => {
|
||||
reporter.name = "no-svg-fill-context-fill";
|
||||
|
||||
const svg = $.find("svg");
|
||||
const fill = svg.attr("fill");
|
||||
if (fill === "context-fill") {
|
||||
reporter.error(
|
||||
"Fill attribute on svg element must not be set to 'context-fill'",
|
||||
svg[0],
|
||||
ast
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
ignore: [
|
||||
"build/**",
|
||||
"l10n/**",
|
||||
"docs/**",
|
||||
"node_modules/**",
|
||||
"external/bcmaps/**",
|
||||
"external/builder/fixtures/**",
|
||||
"external/builder/fixtures_babel/**",
|
||||
"external/quickjs/**",
|
||||
"test/tmp/**",
|
||||
"test/pdfs/**",
|
||||
"web/locale/**",
|
||||
"*~/**",
|
||||
],
|
||||
};
|
||||
204
AGENTS.md
Normal file
@@ -0,0 +1,204 @@
|
||||
## Overview
|
||||
|
||||
PDF.js is a Portable Document Format (PDF) viewer built with JavaScript, HTML5 Canvas, and CSS. It's a Mozilla project that provides a general-purpose, web standards-based platform for parsing and rendering PDFs without requiring native code or plugins.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Development Server
|
||||
```bash
|
||||
npx gulp server
|
||||
```
|
||||
Then open http://localhost:8888/web/viewer.html to view the PDF viewer. Test PDFs are available at http://localhost:8888/test/pdfs/?frame
|
||||
|
||||
### Building
|
||||
|
||||
Build for modern browsers:
|
||||
```bash
|
||||
npx gulp generic
|
||||
```
|
||||
|
||||
This generates `pdf.js` and `pdf.worker.js` in `build/generic/build/`.
|
||||
|
||||
Build for distribution (creates pdfjs-dist package):
|
||||
```bash
|
||||
npx gulp dist
|
||||
npx gulp dist-install # Build and install locally
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Run all tests:
|
||||
```bash
|
||||
npx gulp test
|
||||
```
|
||||
|
||||
Run unit tests only:
|
||||
```bash
|
||||
npx gulp unittest
|
||||
```
|
||||
|
||||
Run integration tests (browser-based tests using Puppeteer):
|
||||
```bash
|
||||
npx gulp integrationtest
|
||||
```
|
||||
|
||||
Run font tests:
|
||||
```bash
|
||||
npx gulp fonttest
|
||||
```
|
||||
|
||||
Run a single test file by modifying test/test_manifest.json or using test runner options.
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
Lint JavaScript:
|
||||
```bash
|
||||
npx gulp lint
|
||||
```
|
||||
|
||||
Format code (uses Prettier and ESLint):
|
||||
```bash
|
||||
npx eslint --fix <file>
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
Run TypeScript type checking:
|
||||
```bash
|
||||
npx gulp typestest
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Structure
|
||||
|
||||
PDF.js has a multi-layer architecture that separates concerns between PDF parsing, rendering, and UI:
|
||||
|
||||
#### 1. Core Layer (`src/core/`)
|
||||
The core layer handles PDF parsing and interpretation. Key responsibilities:
|
||||
- **PDF parsing**: Parsing PDF structure, cross-reference tables, streams
|
||||
- **Font handling**: CFF, TrueType, Type1 font parsing and conversion (`font.js`, `fonts.js`, `cff_*.js`, `type1_*.js`)
|
||||
- **Image decoding**: JPEG, JBIG2, JPX/JPEG2000 decoders
|
||||
- **Operators**: Processing PDF drawing operators (`operator_list.js`, `evaluator.js`)
|
||||
- **XFA Forms**: XML Forms Architecture support (`src/core/xfa/`)
|
||||
- **Color spaces**: ICC profiles, device color spaces (`colorspace.js`, `icc_colorspace.js`)
|
||||
- Runs in a Web Worker for performance isolation
|
||||
|
||||
Entry point: `src/pdf.worker.js`
|
||||
|
||||
#### 2. Display Layer (`src/display/`)
|
||||
The display layer provides the API for rendering PDFs to canvas and managing documents. Key components:
|
||||
- **API**: Main public API (`api.js`) - `PDFDocumentProxy`, `PDFPageProxy`, `getDocument()`
|
||||
- **Canvas rendering**: Renders PDF operations to HTML5 canvas (`canvas.js`)
|
||||
- **Text layer**: Extracts and positions text for selection/search (`text_layer.js`)
|
||||
- **Annotation layer**: Renders and handles PDF annotations (`annotation_layer.js`)
|
||||
- **Editor layer**: Supports PDF editing (annotations, highlights, stamps) (`editor/`)
|
||||
- **Metadata**: Parses XMP metadata (`metadata.js`)
|
||||
- **Streams**: Handles PDF data fetching (fetch, network, node) (`fetch_stream.js`, `network.js`, `node_stream.js`)
|
||||
|
||||
Entry point: `src/pdf.js`
|
||||
|
||||
#### 3. Scripting Layer (`src/scripting_api/`)
|
||||
Implements JavaScript execution for interactive PDFs (form calculations, validations, button actions).
|
||||
- Sandboxed execution environment
|
||||
- Implements Acrobat JavaScript API objects (App, Doc, Field, etc.)
|
||||
|
||||
Entry points: `src/pdf.scripting.js`, `src/pdf.sandbox.js`
|
||||
|
||||
#### 4. Web Viewer (`web/`)
|
||||
The complete PDF viewer application with UI. Key components:
|
||||
- **Main app**: Application orchestration (`app.js`)
|
||||
- **Viewer**: Page rendering and layout (`pdf_viewer.js`, `pdf_page_view.js`)
|
||||
- **Toolbar**: Zoom, page navigation, print, download controls
|
||||
- **Sidebar**: Thumbnails, outlines, attachments (`pdf_sidebar.js`, `pdf_thumbnail_view.js`, `pdf_outline_viewer.js`)
|
||||
- **Find controller**: Text search functionality (`pdf_find_controller.js`)
|
||||
- **Annotation editors**: UI for creating/editing annotations (`annotation_editor_layer_builder.js`)
|
||||
- **Presentation mode**: Full-screen presentation (`pdf_presentation_mode.js`)
|
||||
|
||||
Entry point: `web/viewer.html` + `web/viewer.mjs`
|
||||
|
||||
#### 5. Shared Utilities (`src/shared/`)
|
||||
Common utilities used across layers:
|
||||
- **Message handling**: Worker communication (`message_handler.js`)
|
||||
- **Utilities**: Common functions and constants (`util.js`)
|
||||
- **Image utilities**: Image processing helpers (`image_utils.js`)
|
||||
|
||||
### Worker Communication
|
||||
|
||||
PDF.js uses a Web Worker architecture:
|
||||
- Main thread (`display` layer) communicates with worker thread (`core` layer) via `MessageHandler`
|
||||
- Keeps PDF parsing off the main thread for better performance
|
||||
- Messages include: page rendering requests, text content extraction, metadata queries
|
||||
|
||||
### Build System
|
||||
|
||||
- Uses **Gulp** for build orchestration (`gulpfile.mjs`)
|
||||
- **Webpack** bundles modules into browser-compatible formats
|
||||
- **Babel** transpiles for browser compatibility (configurable targets in gulpfile)
|
||||
- Preprocessor replaces build-time constants (e.g., `typeof PDFJSDev !== "undefined"` checks)
|
||||
- Multiple build targets: generic, components, minified, legacy (older browser support)
|
||||
|
||||
### External Dependencies
|
||||
|
||||
Located in `external/`:
|
||||
- **bcmaps**: Binary CMaps for CJK fonts
|
||||
- **standard_fonts**: Core 14 PDF fonts metrics
|
||||
- **cmapscompress**: Tools for compressing CMaps
|
||||
- **openjpeg**: JPEG2000 decoder (WASM)
|
||||
- **quickjs**: JavaScript engine for sandboxed execution
|
||||
|
||||
### Translations
|
||||
|
||||
Translations in `l10n/` are imported from Mozilla Firefox Nightly. Only the file l10n/en-US/viewer.ftl can be updated.
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Adding New Features
|
||||
|
||||
When adding features that span multiple layers:
|
||||
1. Start with the `core` layer if parsing/interpretation changes are needed
|
||||
2. Update the `display` layer API if new capabilities need exposure
|
||||
3. Modify the `web` viewer if UI changes are required
|
||||
4. Ensure worker communication handles new message types
|
||||
|
||||
### Preprocessor Directives
|
||||
|
||||
Code uses preprocessor checks for build-time conditionals:
|
||||
```javascript
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) {
|
||||
// Generic build-specific code
|
||||
}
|
||||
```
|
||||
|
||||
Common flags: `GENERIC`, `MOZCENTRAL`, `CHROME`, `MINIFIED`, `TESTING`, `LIB`, `SKIP_BABEL`, `IMAGE_DECODERS`
|
||||
|
||||
### Testing
|
||||
|
||||
- Unit tests use Jasmine framework (`test/unit/`)
|
||||
- Integration tests use Puppeteer for browser automation (`test/integration/`)
|
||||
- Test PDFs downloaded from manifest (`test/test_manifest.json`)
|
||||
- Reference images for visual regression testing (`test/ref/`)
|
||||
|
||||
### Code Style
|
||||
|
||||
- Uses ESLint with custom configuration (`eslint.config.mjs`)
|
||||
- Prettier for formatting
|
||||
- Stylelint for CSS
|
||||
- No semicolons required (ASI enabled)
|
||||
- Single quotes for strings
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
- Keep PRs focused on a single issue
|
||||
- Provide a test PDF if the issue is PDF-specific
|
||||
- Ensure tests pass (`npx gulp test`)
|
||||
- Run linting (`npx gulp lint`)
|
||||
- Follow existing code patterns
|
||||
- Don't modify translations directly (they come from Firefox)
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Core parsing runs in a Web Worker - keep main thread work minimal
|
||||
- Canvas rendering can be expensive - use appropriate scale factors
|
||||
- Text layer generation is separate from rendering - can be deferred
|
||||
- Annotation layer is optional - only enable when needed
|
||||
33
AUTHORS
Normal file
@@ -0,0 +1,33 @@
|
||||
This is an (incomplete) list of people who have contributed to the
|
||||
codebase which lives in this repository. If you make a contribution
|
||||
here, you may add your name and, optionally, email address in the
|
||||
appropriate place.
|
||||
|
||||
Adil Allawi <@ironymark>
|
||||
Andreas Gal <gal@mozilla.com>
|
||||
Artur Adib <aadib@mozilla.com>
|
||||
Brendan Dahl <bdahl@mozilla.com>
|
||||
Bill Walker <bwalker@mozilla.com>
|
||||
Calixte Denizet <calixte@mozilla.com>
|
||||
Chris G Jones <cjones@mozilla.com>
|
||||
David Quintana <gigaherz@gmail.com>
|
||||
Emily Wachowiak <ewachowiak@mozilla.com>
|
||||
Felix Kälberer <@fkaelberer>
|
||||
Jakob Miland <saebekassebil@gmail.com>
|
||||
Jonas Jenwald <jonas.jenwald@gmail.com>
|
||||
Julian Viereck
|
||||
Justin D'Arcangelo <justindarc@gmail.com>
|
||||
Kalervo Kujala
|
||||
Marco Castelluccio <mcastelluccio@mozilla.com>
|
||||
Marie-Lilas Onanga Ozavino <monangaozavino@mozilla.com>
|
||||
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
|
||||
Ophir Lojkine <@lovasoa>
|
||||
Ryan Casey <rcasey@mozilla.com>
|
||||
Rob Wu <rob@robwu.nl>
|
||||
Shaon Barman <shaon.barman@gmail.com>
|
||||
Sehyun Park <premed055515@gmail.com>
|
||||
Tim van der Meij <info@timvandermeij.nl>
|
||||
Vivin Paliath <vivin.paliath@gmail.com>
|
||||
Vivien Nicolas <21@vingtetun.org>
|
||||
Yury Delendik <ydelendik@mozilla.com>
|
||||
waddlesplash <@waddlesplash>
|
||||
15
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Community Participation Guidelines
|
||||
|
||||
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
|
||||
For more details, please read the
|
||||
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
||||
|
||||
## How to Report
|
||||
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
|
||||
|
||||
<!--
|
||||
## Project Specific Etiquette
|
||||
|
||||
In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
|
||||
Please update for your project.
|
||||
-->
|
||||
5
EXPORT
Normal file
@@ -0,0 +1,5 @@
|
||||
PDF.js is publicly available software not subject to the Export Administration
|
||||
Regulations (EAR) per EAR 734.3(b) and 734.7. Because PDF.js is not subject
|
||||
to the EAR it does not have an Export Control Classification Number (ECCN).
|
||||
Mozilla has completed the notification for PDF.js publicly available encryption
|
||||
source code per EAR 742.15(b).
|
||||
177
LICENSE
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
206
README.md
@@ -1,2 +1,206 @@
|
||||
# dd-pdf.js
|
||||
# PDF.js [](https://github.com/mozilla/pdf.js/actions/workflows/ci.yml?query=branch%3Amaster) [](https://codecov.io/gh/mozilla/pdf.js)
|
||||
|
||||
[PDF.js](https://mozilla.github.io/pdf.js/) is a Portable Document Format (PDF) viewer that is built with HTML5.
|
||||
|
||||
PDF.js is community-driven and supported by Mozilla. Our goal is to
|
||||
create a general-purpose, web standards-based platform for parsing and
|
||||
rendering PDFs.
|
||||
|
||||
## Contributing
|
||||
|
||||
PDF.js is an open source project and always looking for more contributors. To
|
||||
get involved, visit:
|
||||
|
||||
+ [Issue Reporting Guide](https://github.com/mozilla/pdf.js/blob/master/.github/CONTRIBUTING.md)
|
||||
+ [Code Contribution Guide](https://github.com/mozilla/pdf.js/wiki/Contributing)
|
||||
+ [Frequently Asked Questions](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions)
|
||||
+ [Good Beginner Bugs](https://github.com/mozilla/pdf.js/issues?q=is%3Aissue%20state%3Aopen%20label%3Agood-beginner-bug)
|
||||
+ [Projects](https://github.com/mozilla/pdf.js/projects)
|
||||
|
||||
Feel free to stop by our [Matrix room](https://chat.mozilla.org/#/room/#pdfjs:mozilla.org) for questions or guidance.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Online demo
|
||||
|
||||
Please note that the "Modern browsers" version assumes native support for the
|
||||
latest JavaScript features; please also see [this wiki page](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support).
|
||||
|
||||
+ Modern browsers: https://mozilla.github.io/pdf.js/web/viewer.html
|
||||
|
||||
+ Older browsers: https://mozilla.github.io/pdf.js/legacy/web/viewer.html
|
||||
|
||||
### Browser Extensions
|
||||
|
||||
#### Firefox
|
||||
|
||||
PDF.js is built into version 19+ of Firefox.
|
||||
|
||||
#### Chrome
|
||||
|
||||
+ The official extension for Chrome can be installed from the [Chrome Web Store](https://chrome.google.com/webstore/detail/pdf-viewer/oemmndcbldboiebfnladdacbdfmadadm).
|
||||
*This extension is maintained by [@Rob--W](https://github.com/Rob--W).*
|
||||
+ Build Your Own - Get the code as explained below and issue `npx gulp chromium`. Then open
|
||||
Chrome, go to `Tools > Extension` and load the (unpackaged) extension from the
|
||||
directory `build/chromium`.
|
||||
|
||||
### PDF debugger
|
||||
|
||||
Browse the internal structure of a PDF document with https://mozilla.github.io/pdf.js/internal-viewer/web/debugger.html
|
||||
|
||||
## Getting the Code
|
||||
|
||||
To get a local copy of the current code, clone it using git:
|
||||
|
||||
$ git clone https://github.com/mozilla/pdf.js.git
|
||||
$ cd pdf.js
|
||||
|
||||
Next, install Node.js via the [official package](https://nodejs.org) or via
|
||||
[nvm](https://github.com/creationix/nvm). If everything worked out, install
|
||||
all dependencies for PDF.js:
|
||||
|
||||
$ npm install
|
||||
|
||||
Finally, you need to start a local web server as some browsers do not allow opening
|
||||
PDF files using a `file://` URL. Run:
|
||||
|
||||
$ npx gulp server
|
||||
|
||||
and then you can open:
|
||||
|
||||
+ http://localhost:8888/web/viewer.html
|
||||
|
||||
Please keep in mind that this assumes the latest version of Mozilla Firefox; refer to [Building PDF.js](https://github.com/mozilla/pdf.js/blob/master/README.md#building-pdfjs) for non-development usage of the PDF.js library.
|
||||
|
||||
It is also possible to view all test PDF files on the right side by opening:
|
||||
|
||||
+ http://localhost:8888/test/pdfs/?frame
|
||||
|
||||
## Building PDF.js
|
||||
|
||||
In order to bundle all `src/` files into two production scripts and build the generic
|
||||
viewer, run:
|
||||
|
||||
$ npx gulp generic
|
||||
|
||||
If you need to support older browsers, run:
|
||||
|
||||
$ npx gulp generic-legacy
|
||||
|
||||
This will generate `pdf.js` and `pdf.worker.js` in the `build/generic/build/` directory (respectively `build/generic-legacy/build/`).
|
||||
Both scripts are needed but only `pdf.js` needs to be included since `pdf.worker.js` will
|
||||
be loaded by `pdf.js`. The PDF.js files are large and should be minified for production.
|
||||
|
||||
## Code coverage
|
||||
|
||||
We track how much of the code is exercised by the test suite on
|
||||
[Codecov](https://codecov.io/gh/mozilla/pdf.js) (see the badge at the top of this
|
||||
file).
|
||||
|
||||
### How it is collected
|
||||
|
||||
When coverage is enabled, the build instruments the bundled code with
|
||||
[`babel-plugin-istanbul`](https://github.com/istanbuljs/babel-plugin-istanbul),
|
||||
which adds counters that record every line, branch and function that runs:
|
||||
|
||||
+ For browser-based tests (unit, integration and reference tests) the
|
||||
instrumented code runs in the browser, fills a global `window.__coverage__`
|
||||
object, and the test runner collects it from each browser session, merges the
|
||||
results, and writes the report.
|
||||
+ For the Node-based unit tests (`unittestcli`) the raw data is written to
|
||||
`build/tmp/unittestcli-coverage.json` and turned into a report afterwards.
|
||||
|
||||
### Collecting coverage locally
|
||||
|
||||
Add the `--coverage` flag to any of the test tasks, for example:
|
||||
|
||||
$ npx gulp unittest --coverage # browser unit tests
|
||||
$ npx gulp unittestcli --coverage # Node unit tests
|
||||
$ npx gulp integrationtest --coverage # Puppeteer integration tests
|
||||
$ npx gulp botbrowsertest --coverage # reference tests
|
||||
|
||||
The following options control the output:
|
||||
|
||||
| Option | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `--coverage` | Enable coverage collection. | off |
|
||||
| `--coverage-output <dir>` | Directory where the report is written. | `build/coverage` |
|
||||
| `--coverage-formats <list>` | Comma-separated list of formats: `info`, `html`, `json`, `text`, `cobertura`, `clover`. | `info` |
|
||||
| `--coverage-per-test` | Also build a per-test index (see below). | off |
|
||||
|
||||
By default the report is written to `build/coverage` in the `info` format, i.e.
|
||||
an [LCOV](https://github.com/linux-test-project/lcov) `lcov.info` file (the same
|
||||
format that is uploaded to Codecov). Use `--coverage-formats html` to get a
|
||||
browsable HTML report instead, or pass several formats at once, e.g.
|
||||
`--coverage-formats info,html`.
|
||||
|
||||
### Finding which tests cover a given line
|
||||
|
||||
Run a browser test task with `--coverage-per-test` to build an index
|
||||
(`per-test-index.json`) in the coverage directory, then query it to list the
|
||||
tests that exercised a specific source line or function:
|
||||
|
||||
$ npx gulp botbrowsertest --coverage-per-test
|
||||
$ npx gulp coverage_search --code="canvas.js::205"
|
||||
$ npx gulp coverage_search --code="canvas.js::drawImageAtIntegerCoords"
|
||||
|
||||
### Continuous integration
|
||||
|
||||
On every push and pull request three GitHub Actions workflows collect coverage
|
||||
and upload it to Codecov, each tagged with its own Codecov *flag* so the test
|
||||
types can be told apart:
|
||||
|
||||
| Workflow | Task | Codecov flag |
|
||||
| --- | --- | --- |
|
||||
| `unit_tests.yml` | `unittest` | `unittest` |
|
||||
| `integration_tests.yml` | `integrationtest` | `integrationtest` |
|
||||
| `coverage_browser_tests.yml` | `botbrowsertest` | `browsertest` |
|
||||
|
||||
## Using PDF.js in a web application
|
||||
|
||||
To use PDF.js in a web application you can choose to use a pre-built version of the library
|
||||
or to build it from source. We supply pre-built versions for usage with NPM under
|
||||
the `pdfjs-dist` name. For more information and examples please refer to the
|
||||
[wiki page](https://github.com/mozilla/pdf.js/wiki/Setup-pdf.js-in-a-website) on this subject.
|
||||
|
||||
## Including via a CDN
|
||||
|
||||
PDF.js is hosted on several free CDNs:
|
||||
- https://www.jsdelivr.com/package/npm/pdfjs-dist
|
||||
- https://cdnjs.com/libraries/pdf.js
|
||||
- https://unpkg.com/pdfjs-dist/
|
||||
|
||||
## Learning
|
||||
|
||||
You can play with the PDF.js API directly from your browser using the live demos below:
|
||||
|
||||
+ [Interactive examples](https://mozilla.github.io/pdf.js/examples/index.html#interactive-examples)
|
||||
|
||||
More examples can be found in the [examples folder](https://github.com/mozilla/pdf.js/tree/master/examples/). Some of them are using the pdfjs-dist package, which can be built and installed in this repo directory via `npx gulp dist-install` command.
|
||||
|
||||
For an introduction to the PDF.js code, check out the presentation by our
|
||||
contributor Julian Viereck:
|
||||
|
||||
+ https://www.youtube.com/watch?v=Iv15UY-4Fg8
|
||||
|
||||
More learning resources can be found at:
|
||||
|
||||
+ https://github.com/mozilla/pdf.js/wiki/Additional-Learning-Resources
|
||||
|
||||
The API documentation can be found at:
|
||||
|
||||
+ https://mozilla.github.io/pdf.js/api/
|
||||
|
||||
## Questions
|
||||
|
||||
Check out our FAQs and get answers to common questions:
|
||||
|
||||
+ https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions
|
||||
|
||||
Talk to us on Matrix:
|
||||
|
||||
+ https://chat.mozilla.org/#/room/#pdfjs:mozilla.org
|
||||
|
||||
File an issue:
|
||||
|
||||
+ https://github.com/mozilla/pdf.js/issues/new/choose
|
||||
|
||||
3
codecov.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: true
|
||||
11
docs/contents/api/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: API
|
||||
layout: layout.njk
|
||||
slug: api
|
||||
---
|
||||
|
||||
# API
|
||||
|
||||
The generated API documentation, from the inline comments in [api.js](https://github.com/mozilla/pdf.js/blob/master/src/display/api.js), is available below.
|
||||
|
||||
<iframe src="draft/index.html" title="PDF.js API documentation"></iframe>
|
||||
100
docs/contents/css/a11y-light.css
Normal file
@@ -0,0 +1,100 @@
|
||||
/*!
|
||||
Theme: a11y-light
|
||||
Author: @ericwbailey
|
||||
Maintainer: @ericwbailey
|
||||
|
||||
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||
|
||||
Original source: https://github.com/highlightjs/highlight.js/blob/main/src/styles/a11y-light.css
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
background: #fefefe;
|
||||
color: #545454;
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
/* Red */
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-regexp,
|
||||
.hljs-deletion {
|
||||
color: #d91e18;
|
||||
}
|
||||
|
||||
/* Orange */
|
||||
.hljs-number,
|
||||
.hljs-built_in,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params,
|
||||
.hljs-meta,
|
||||
.hljs-link {
|
||||
color: #aa5d00;
|
||||
}
|
||||
|
||||
/* Yellow */
|
||||
.hljs-attribute {
|
||||
color: #aa5d00;
|
||||
}
|
||||
|
||||
/* Green */
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-addition {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
/* Blue */
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #007faa;
|
||||
}
|
||||
|
||||
/* Purple */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #7928a1;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (-ms-high-contrast: active) {
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-comment,
|
||||
.hljs-link,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-params,
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-type,
|
||||
.hljs-quote {
|
||||
color: highlight;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
7
docs/contents/css/bootstrap.min.css
vendored
Normal file
40
docs/contents/css/main.css
Normal file
@@ -0,0 +1,40 @@
|
||||
header {
|
||||
background-color: #f8f8f8;
|
||||
border-bottom: 1px solid #e5e7e8;
|
||||
|
||||
.navbar-brand {
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 50px 0;
|
||||
|
||||
.description {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
height: calc(0.55 * 100vh);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
color: #777777;
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
100
docs/contents/examples/index.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Examples
|
||||
layout: layout.njk
|
||||
slug: examples
|
||||
---
|
||||
|
||||
## Hello World Walkthrough
|
||||
|
||||
[Full source](https://github.com/mozilla/pdf.js/blob/master/examples/learning/helloworld.html)
|
||||
|
||||
PDF.js heavily relies on the use of [Promises](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise). If promises are new to you, it's recommended you become familiar with them before continuing on.
|
||||
|
||||
This tutorial shows how PDF.js can be used as a library in a web browser.
|
||||
[examples/](https://github.com/mozilla/pdf.js/tree/master/examples) provides more examples, including usage in Node.js (at [examples/node/](https://github.com/mozilla/pdf.js/tree/master/examples/node)).
|
||||
|
||||
### Document
|
||||
|
||||
The object structure of PDF.js loosely follows the structure of an actual PDF. At the top level there is a document object. From the document, more information and individual pages can be fetched. To get the document:
|
||||
|
||||
```js
|
||||
pdfjsLib.getDocument({ url: "helloworld.pdf" })
|
||||
```
|
||||
|
||||
Remember though that PDF.js uses promises, and the above will return a `PDFDocumentLoadingTask` instance that has a `promise` property which is resolved with the document object.
|
||||
|
||||
```js
|
||||
var loadingTask = pdfjsLib.getDocument({ url: "helloworld.pdf" });
|
||||
loadingTask.promise.then(function(pdf) {
|
||||
// you can now use *pdf* here
|
||||
});
|
||||
```
|
||||
|
||||
### Page
|
||||
Now that we have the document, we can get a page. Again, this uses promises.
|
||||
|
||||
```js
|
||||
pdf.getPage(1).then(function(page) {
|
||||
// you can now use *page* here
|
||||
});
|
||||
```
|
||||
|
||||
### Rendering the Page
|
||||
Each PDF page has its own viewport which defines the size in pixels(72DPI) and initial rotation. By default the viewport is scaled to the original size of the PDF, but this can be changed by modifying the viewport. When the viewport is created, an initial transformation matrix will also be created that takes into account the desired scale, rotation, and it transforms the coordinate system (the 0,0 point in PDF documents the bottom-left whereas canvas 0,0 is top-left).
|
||||
|
||||
```js
|
||||
var scale = 1.5;
|
||||
var viewport = page.getViewport({ scale: scale, });
|
||||
// Support HiDPI-screens.
|
||||
var outputScale = window.devicePixelRatio || 1;
|
||||
|
||||
var canvas = document.getElementById("the-canvas");
|
||||
var context = 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";
|
||||
|
||||
var transform = outputScale !== 1
|
||||
? [outputScale, 0, 0, outputScale, 0, 0]
|
||||
: null;
|
||||
|
||||
var renderContext = {
|
||||
canvasContext: context,
|
||||
transform: transform,
|
||||
viewport: viewport
|
||||
};
|
||||
page.render(renderContext);
|
||||
```
|
||||
|
||||
Alternatively, if you want the canvas to render to a certain pixel size you could do the following:
|
||||
|
||||
```js
|
||||
var desiredWidth = 100;
|
||||
var viewport = page.getViewport({ scale: 1, });
|
||||
var scale = desiredWidth / viewport.width;
|
||||
var scaledViewport = page.getViewport({ scale: scale, });
|
||||
```
|
||||
|
||||
## Interactive examples
|
||||
|
||||
### Hello World with document load error handling
|
||||
|
||||
The example demonstrates how promises can be used to handle errors during loading.
|
||||
It also demonstrates how to wait until a page is loaded and rendered.
|
||||
|
||||
<script async src="https://jsfiddle.net/pdfjs/9engc9mw/embed/html,css,result/"></script>
|
||||
|
||||
### Hello World using base64 encoded PDF
|
||||
|
||||
The PDF.js can accept any decoded base64 data as an array.
|
||||
|
||||
<script async src="https://jsfiddle.net/pdfjs/cq0asLqz/embed/html,css,result/"></script>
|
||||
|
||||
### Previous/Next example
|
||||
|
||||
The same canvas cannot be used to perform to draw two pages at the same time --
|
||||
the example demonstrates how to wait on previous operation to be complete.
|
||||
|
||||
<script async src="https://jsfiddle.net/pdfjs/wagvs9Lf/embed/html,css,result/"></script>
|
||||
127
docs/contents/getting_started/index.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Getting Started
|
||||
layout: layout.njk
|
||||
slug: getting_started
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
An introduction to PDF.js with examples.
|
||||
|
||||
## Introduction
|
||||
|
||||
Before downloading PDF.js please take a moment to understand the different layers of the PDF.js project.
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Layer</th>
|
||||
<th>About</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Core</td>
|
||||
<td>The core layer is where a binary PDF is parsed and interpreted. This layer is the foundation for all subsequent layers. It is not documented here because using it directly is considered an advanced usage and the API is likely to change. For an example of using the core layer see the <a href="https://github.com/brendandahl/pdf.js.utils/tree/master/browser">PDF Object Browser</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Display</td>
|
||||
<td>The display layer takes the core layer and exposes an easier to use API to render PDFs and get other information out of a document. This API is what the version number is based on.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Viewer</td>
|
||||
<td>The viewer is built on the display layer and is the UI for PDF viewer in Firefox and the other browser extensions within the project. It can be a good starting point for building your own viewer. <em>However, we do ask if you plan to embed the viewer in your own site, that it not just be an unmodified version. Please re-skin it or build upon it.</em></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Download
|
||||
|
||||
Please refer to [this wiki page](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support) for information about supported browsers.
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h3>Prebuilt (modern browsers)</h3>
|
||||
<p>
|
||||
Includes the generic build of PDF.js and the viewer.
|
||||
</p>
|
||||
<a type="button" class="btn btn-primary" href="https://github.com/mozilla/pdf.js/releases/download/vSTABLE_VERSION/pdfjs-STABLE_VERSION-dist.zip">Stable (vSTABLE_VERSION)</a>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h3>Prebuilt (older browsers)</h3>
|
||||
<p>
|
||||
Includes the generic build of PDF.js and the viewer.
|
||||
</p>
|
||||
<a type="button" class="btn btn-primary" href="https://github.com/mozilla/pdf.js/releases/download/vSTABLE_VERSION/pdfjs-STABLE_VERSION-legacy-dist.zip">Stable (vSTABLE_VERSION)</a>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h3>Source</h3>
|
||||
To get a local copy of the current code, clone it using git:
|
||||
<pre><code>$ git clone https://github.com/mozilla/pdf.js.git
|
||||
$ cd pdf.js
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Including via a CDN
|
||||
|
||||
PDF.js is hosted on several free CDNs:
|
||||
- https://www.jsdelivr.com/package/npm/pdfjs-dist
|
||||
- https://cdnjs.com/libraries/pdf.js
|
||||
- https://unpkg.com/pdfjs-dist/
|
||||
|
||||
## File Layout Overview
|
||||
|
||||
Note that we only mention the most relevant files and folders.
|
||||
|
||||
### Prebuilt
|
||||
|
||||
```plaintext
|
||||
├── build/
|
||||
│ ├── pdf.mjs - display layer
|
||||
│ ├── pdf.mjs.map - display layer's source map
|
||||
│ ├── pdf.worker.mjs - core layer
|
||||
│ └── pdf.worker.mjs.map - core layer's source map
|
||||
├── web/
|
||||
│ ├── cmaps/ - character maps (required by core)
|
||||
│ ├── compressed.tracemonkey-pldi-09.pdf - PDF file for testing purposes
|
||||
│ ├── images/ - images for the viewer and annotation icons
|
||||
│ ├── locale/ - translation files
|
||||
│ ├── viewer.css - viewer style sheet
|
||||
│ ├── viewer.html - viewer layout
|
||||
│ ├── viewer.mjs - viewer layer
|
||||
│ └── viewer.mjs.map - viewer layer's source map
|
||||
└── LICENSE
|
||||
```
|
||||
|
||||
### Source
|
||||
|
||||
```plaintext
|
||||
├── docs/ - website source code
|
||||
├── examples/ - simple usage examples
|
||||
├── extensions/ - browser extension source code
|
||||
├── external/ - third party code
|
||||
├── l10n/ - translation files
|
||||
├── src/
|
||||
│ ├── core/ - core layer
|
||||
│ ├── display/ - display layer
|
||||
│ ├── shared/ - shared code between the core and display layers
|
||||
│ ├── interfaces.js - interface definitions for the core/display layers
|
||||
│ └── pdf.*.js - wrapper files for bundling
|
||||
├── test/ - unit, font, reference, and integration tests
|
||||
├── web/ - viewer layer
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── gulpfile.mjs - build scripts/logic
|
||||
├── package-lock.json - pinned dependency versions
|
||||
└── package.json - package definition and dependencies
|
||||
```
|
||||
|
||||
## Trying the Viewer
|
||||
|
||||
With the prebuilt or source version, open `web/viewer.html` in a browser and the test pdf should load. Note: the worker is not enabled for file:// urls, so use a server. If you're using the source build and have node, you can run `npx gulp server`.
|
||||
|
||||
## More Information
|
||||
|
||||
For a further walkthrough of a minimal viewer, see the hello world example. More documentation can be found in our [wiki](https://github.com/mozilla/pdf.js/wiki) too.
|
||||
BIN
docs/contents/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 32 KiB |
41
docs/contents/images/logo.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64">
|
||||
<path
|
||||
d="M 4.8364028,0.4891005 32.096378,4.5726641 59.163597,0.4891005 54.680408,57.805097 32.096378,63.510899 8.3116209,57.805097 z"
|
||||
style="fill:#e5e7e8;fill-opacity:1;fill-rule:nonzero;stroke:#cccccc" />
|
||||
<path
|
||||
d="M 32.096378,10.745857 53.925414,6.8301117 51.016574,53.81906 32.096378,58.517955 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<rect
|
||||
width="34.027256"
|
||||
height="19.136194"
|
||||
x="3.7557135"
|
||||
y="22.431904"
|
||||
style="fill:#ff2600;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<rect
|
||||
width="23.480518"
|
||||
height="19.136194"
|
||||
x="36.763767"
|
||||
y="22.431904"
|
||||
style="fill:#ff501a;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<g transform="matrix(0.42778543,0,0,0.42778543,58.617711,9.6737064)">
|
||||
<path
|
||||
d="m -120.53125,34.59375 0,35.1875 6.53125,0 0,-5.9375 0,-5.96875 8.875,0 4.15625,-3.71875 0,-7.71875 0,-7.71875 -4.21875,-4.125 -15.34375,0 z m 6.53125,6.8125 6.21875,0 0,10.21875 -6.21875,0 0,-10.21875 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="m -98.125,34.59375 0,35.1875 16.125,0 3.75,-3.625 0,-27.96875 -3.96875,-3.59375 -15.90625,0 z m 6.8125,6.8125 6.8125,0 0,21.5625 -6.8125,0 0,-21.5625 z" id="path3056"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="m -74.856072,34.602929 c 5.485697,0 10.971394,0 16.457091,0 0,2.269943 0,4.539887 0,6.80983 -3.404915,0 -6.809831,0 -10.214746,0 0,2.472069 0,4.944138 0,7.416206 2.93201,0.110496 5.864021,-0.110494 8.796031,0 l 0,3.366025 0,3.375914 c -2.93201,0.110495 -5.864021,-0.110495 -8.796031,0 l 0,7.146446 0,7.069702 c -2.080782,0 -4.161563,0 -6.242345,0 0,-11.728041 0,-23.456082 0,-35.184123 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="m -42.8813,67.942723 -2.181762,-1.844329 c 0,-1.986871 0,-3.973742 0,-5.960613 2.175363,6.7e-4 4.350725,0.0013 6.526088,0.002 0,0.94581 0,1.891619 0,2.837429 1.891619,0 3.783239,0 5.674858,0 0,-9.458098 0,-18.916195 0,-28.374293 2.175363,-1.26e-4 4.350725,-2.52e-4 6.526088,-3.78e-4 0,10.498614 0,20.997229 0,31.495843 -1.454508,1.229554 -2.909019,2.459106 -4.363529,3.688658 -3.333325,0 -6.666651,0 -9.999976,0 -0.727237,-0.614768 -1.454563,-1.229595 -2.181767,-1.844308 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="m -21.316836,67.942723 -2.109122,-1.844329 0,-5.978379 c 2.061011,0 4.392437,0.01978 6.453448,0.01978 0,0.94581 0,1.891619 0,2.837429 2.269943,0 4.539887,0 6.80983,0 0,-2.459105 0,-4.918211 0,-7.377316 -3.196954,0 -6.393909,0 -9.590863,0 l -3.745055,-3.688659 0,-6.80983 0,-6.80983 3.745055,-3.688658 c 3.863156,0 7.726313,0 11.5894692,0 l 4.2511106,3.688658 c -0.00204,1.797039 -0.00472,3.594077 -0.00737,5.391115 -2.0807816,0 -4.1615632,0 -6.2423448,0 0,-0.756648 0,-1.513295 0,-2.269943 -2.269943,0 -4.539887,0 -6.80983,0 0,2.459105 0,4.918211 0,7.377316 2.990126,0 5.980253,0 8.9703792,0 1.3605964,1.204585 4.0817956,3.613749 4.0817956,3.613749 0,4.555246 0,9.110492 0,13.665738 l -2.0892845,1.858745 -2.0892901,1.858745 -5.5180792,0 -5.51808,0 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
13
docs/contents/index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Home
|
||||
layout: layout.njk
|
||||
slug: home
|
||||
---
|
||||
|
||||
<h1 class="text-center">PDF.js</h1>
|
||||
<p class="text-center description">A general-purpose, web standards-based platform for parsing and rendering PDFs.</p>
|
||||
<p class="text-center">
|
||||
<a type="button" class="btn btn-outline-dark" href="/getting_started/#download">Download</a>
|
||||
<a type="button" class="btn btn-outline-dark" href="https://github.com/mozilla/pdf.js#online-demo">Demo</a>
|
||||
<a type="button" class="btn btn-outline-dark" href="https://github.com/mozilla/pdf.js">GitHub Project</a>
|
||||
</p>
|
||||
7
docs/contents/js/bootstrap.min.js
vendored
Normal file
2
docs/contents/js/jquery-3.7.1.min.js
vendored
Normal file
56
docs/templates/layout.njk
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ sitename }} - {{ title }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="device-width, initial-scale=1.0">
|
||||
<script src="/js/jquery-3.7.1.min.js"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/a11y-light.css">
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<div class="navbar-brand"><img src="/images/logo.svg"></div>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {{ 'active' if slug == 'home' else '' }}">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item {{ 'active' if slug == 'getting_started' else '' }}">
|
||||
<a class="nav-link" href="/getting_started">Getting started</a>
|
||||
</li>
|
||||
<li class="nav-item {{ 'active' if slug == 'examples' else '' }}">
|
||||
<a class="nav-link" href="/examples">Examples</a>
|
||||
</li>
|
||||
<li class="nav-item {{ 'active' if slug == 'api' else '' }}">
|
||||
<a class="nav-link" href="/api">API</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions">FAQ</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="container">
|
||||
{{ contents | safe }}
|
||||
</main>
|
||||
<footer>
|
||||
<p>© Mozilla and individual contributors</p>
|
||||
<p>
|
||||
PDF.js is licensed under <a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache 2.0</a>,
|
||||
documentation is licensed under <a href="https://creativecommons.org/licenses/by-sa/2.5">CC BY-SA 2.5</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
511
eslint.config.mjs
Normal file
@@ -0,0 +1,511 @@
|
||||
import globals from "globals";
|
||||
|
||||
import import_ from "eslint-plugin-import-x";
|
||||
import jasmine from "eslint-plugin-jasmine";
|
||||
import json from "@eslint/json";
|
||||
import noUnsanitized from "eslint-plugin-no-unsanitized";
|
||||
import perfectionist from "eslint-plugin-perfectionist";
|
||||
import preferMathClamp from "./external/eslint_plugins/prefer-math-clamp.mjs";
|
||||
import prettierRecommended from "eslint-plugin-prettier/recommended";
|
||||
import regexpPlugin from "eslint-plugin-regexp";
|
||||
import unicorn from "eslint-plugin-unicorn";
|
||||
|
||||
const jsFiles = folder => {
|
||||
const prefix = folder === "." ? "" : folder + "/";
|
||||
return [prefix + "**/*.js", prefix + "**/*.jsm", prefix + "**/*.mjs"];
|
||||
};
|
||||
|
||||
// Include all files referenced in extensions/chromium/background.js
|
||||
const chromiumExtensionServiceWorkerFiles = [
|
||||
"extensions/chromium/extension-router.js",
|
||||
"extensions/chromium/options/migration.js",
|
||||
"extensions/chromium/pdfHandler.js",
|
||||
"extensions/chromium/preserve-referer.js",
|
||||
"extensions/chromium/suppress-update.js",
|
||||
"extensions/chromium/telemetry.js",
|
||||
];
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"package-lock.json",
|
||||
"**/build/",
|
||||
"**/l10n/",
|
||||
"**/docs/",
|
||||
"**/node_modules/",
|
||||
"external/bcmaps/",
|
||||
"external/brotli/",
|
||||
"external/builder/fixtures/",
|
||||
"external/builder/fixtures_babel/",
|
||||
"external/openjpeg/",
|
||||
"external/qcms/",
|
||||
"external/jbig2/",
|
||||
"external/quickjs/",
|
||||
"test/stats/results/",
|
||||
"test/tmp/",
|
||||
"test/pdfs/",
|
||||
"web/locale/",
|
||||
"web/wasm/",
|
||||
"**/*~/",
|
||||
".{claude,codex,cursor}/",
|
||||
],
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
Base configuration
|
||||
\* ======================================================================== */
|
||||
|
||||
prettierRecommended,
|
||||
{
|
||||
files: jsFiles("."),
|
||||
plugins: regexpPlugin.configs["flat/recommended"].plugins,
|
||||
rules: {
|
||||
...regexpPlugin.configs["flat/recommended"].rules,
|
||||
"regexp/no-legacy-features": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.json", "**/.*.json"],
|
||||
language: "json/json",
|
||||
...json.configs.recommended,
|
||||
},
|
||||
{
|
||||
files: jsFiles("."),
|
||||
ignores: chromiumExtensionServiceWorkerFiles,
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: jsFiles("."),
|
||||
|
||||
plugins: {
|
||||
import: import_.flatConfigs.recommended.plugins["import-x"],
|
||||
json,
|
||||
"no-unsanitized": noUnsanitized,
|
||||
perfectionist,
|
||||
"prefer-math-clamp": preferMathClamp,
|
||||
unicorn,
|
||||
},
|
||||
|
||||
settings: {
|
||||
"import-x/resolver-next": [import_.createNodeResolver()],
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.worker,
|
||||
PDFJSDev: "readonly",
|
||||
__raw_import__: "readonly",
|
||||
__eager_import__: "readonly",
|
||||
},
|
||||
|
||||
ecmaVersion: 2025,
|
||||
sourceType: "module",
|
||||
},
|
||||
|
||||
rules: {
|
||||
"import/export": "error",
|
||||
"import/exports-last": "error",
|
||||
"import/extensions": ["error", "always", { ignorePackages: true }],
|
||||
"import/first": "error",
|
||||
"import/named": "error",
|
||||
"import/no-cycle": "error",
|
||||
"import/no-empty-named-blocks": "error",
|
||||
"import/no-commonjs": "error",
|
||||
"import/no-mutable-exports": "error",
|
||||
"import/no-restricted-paths": [
|
||||
"error",
|
||||
{
|
||||
zones: [
|
||||
{
|
||||
target: "./web",
|
||||
from: "./src",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"import/no-self-import": "error",
|
||||
"import/no-unresolved": [
|
||||
"error",
|
||||
{
|
||||
ignore: [
|
||||
"display",
|
||||
"pdfjs",
|
||||
"pdfjs-lib",
|
||||
"pdfjs-web",
|
||||
"web",
|
||||
"@csstools/postcss-light-dark-function",
|
||||
"fluent-bundle",
|
||||
"fluent-dom",
|
||||
"postcss-dir-pseudo-class",
|
||||
"postcss-nesting",
|
||||
"postcss-values-parser",
|
||||
"stylelint",
|
||||
// See https://github.com/firebase/firebase-admin-node/discussions/1359.
|
||||
"eslint-plugin-perfectionist",
|
||||
],
|
||||
},
|
||||
],
|
||||
"no-unsanitized/method": "error",
|
||||
"no-unsanitized/property": "error",
|
||||
"perfectionist/sort-exports": "error",
|
||||
"perfectionist/sort-named-exports": "error",
|
||||
"unicorn/no-abusive-eslint-disable": "error",
|
||||
"unicorn/no-array-reduce": ["error", { allowSimpleOperations: true }],
|
||||
"unicorn/no-console-spaces": "error",
|
||||
"unicorn/no-instanceof-builtins": "error",
|
||||
"unicorn/no-invalid-remove-event-listener": "error",
|
||||
"unicorn/no-new-buffer": "error",
|
||||
"unicorn/no-single-promise-in-promise-methods": "error",
|
||||
"unicorn/no-typeof-undefined": ["error", { checkGlobalVariables: false }],
|
||||
"unicorn/no-unnecessary-array-flat-depth": "error",
|
||||
"unicorn/no-unnecessary-array-splice-count": "error",
|
||||
"unicorn/no-unnecessary-slice-end": "error",
|
||||
"unicorn/no-useless-collection-argument": "error",
|
||||
"unicorn/no-useless-promise-resolve-reject": "error",
|
||||
"unicorn/no-useless-spread": "error",
|
||||
"unicorn/prefer-array-find": "error",
|
||||
"unicorn/prefer-array-flat": "error",
|
||||
"unicorn/prefer-array-flat-map": "error",
|
||||
"unicorn/prefer-array-index-of": "error",
|
||||
"unicorn/prefer-array-some": "error",
|
||||
"unicorn/prefer-at": "error",
|
||||
"unicorn/prefer-class-fields": "error",
|
||||
"unicorn/prefer-classlist-toggle": "error",
|
||||
"unicorn/prefer-date-now": "error",
|
||||
"unicorn/prefer-dom-node-append": "error",
|
||||
"unicorn/prefer-dom-node-remove": "error",
|
||||
"unicorn/prefer-import-meta-properties": "error",
|
||||
"unicorn/prefer-includes": "error",
|
||||
"unicorn/prefer-logical-operator-over-ternary": "error",
|
||||
"unicorn/prefer-modern-dom-apis": "error",
|
||||
"unicorn/prefer-modern-math-apis": "error",
|
||||
"unicorn/prefer-negative-index": "error",
|
||||
"unicorn/prefer-optional-catch-binding": "error",
|
||||
"unicorn/prefer-regexp-test": "error",
|
||||
"unicorn/prefer-single-call": "error",
|
||||
"unicorn/prefer-string-replace-all": "error",
|
||||
"unicorn/prefer-string-starts-ends-with": "error",
|
||||
"unicorn/prefer-ternary": ["error", "only-single-line"],
|
||||
"unicorn/throw-new-error": "error",
|
||||
|
||||
"prefer-math-clamp/prefer-math-clamp": "error",
|
||||
|
||||
// Possible errors
|
||||
"for-direction": "error",
|
||||
"getter-return": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": ["error", "except-parens"],
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
"no-debugger": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-empty": ["error", { allowEmptyCatch: true }],
|
||||
"no-empty-character-class": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-inner-declarations": ["error", "functions"],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-setter-return": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unsafe-negation": "error",
|
||||
"no-unsafe-optional-chaining": [
|
||||
"error",
|
||||
{ disallowArithmeticOperators: true },
|
||||
],
|
||||
"no-unused-private-class-members": "error",
|
||||
"use-isnan": ["error", { enforceForIndexOf: true }],
|
||||
"valid-typeof": ["error", { requireStringLiterals: true }],
|
||||
|
||||
// Best Practices
|
||||
"accessor-pairs": [
|
||||
"error",
|
||||
{ setWithoutGet: true, enforceForClassMembers: true },
|
||||
],
|
||||
"consistent-return": "error",
|
||||
curly: ["error", "all"],
|
||||
"default-case-last": "error",
|
||||
"dot-notation": "error",
|
||||
eqeqeq: ["error", "always"],
|
||||
"grouped-accessor-pairs": ["error", "getBeforeSet"],
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-pattern": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-global-assign": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-iterator": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-symbol": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-octal": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-return-await": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-catch": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-return": "error",
|
||||
"prefer-object-has-own": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-spread": "error",
|
||||
radix: "error",
|
||||
"wrap-iife": ["error", "any"],
|
||||
yoda: ["error", "never", { exceptRange: true }],
|
||||
|
||||
// Strict Mode
|
||||
strict: ["off", "global"],
|
||||
|
||||
// Variables
|
||||
"no-delete-var": "error",
|
||||
"no-label-var": "error",
|
||||
"no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undef": ["error", { typeof: true }],
|
||||
"no-unused-vars": ["error", { vars: "all", args: "none" }],
|
||||
"no-use-before-define": [
|
||||
"error",
|
||||
{ functions: false, classes: false, variables: false },
|
||||
],
|
||||
|
||||
// Stylistic Issues
|
||||
"lines-between-class-members": ["error", "always"],
|
||||
"max-len": ["error", { code: 1000, comments: 80, ignoreUrls: true }],
|
||||
"new-cap": ["error", { newIsCap: true, capIsNew: false }],
|
||||
"no-array-constructor": "error",
|
||||
"no-multiple-empty-lines": ["error", { max: 1, maxEOF: 0, maxBOF: 1 }],
|
||||
"no-nested-ternary": "error",
|
||||
"no-new-object": "error",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector:
|
||||
"BinaryExpression[operator='instanceof'][right.name='Object']",
|
||||
message: "Use `typeof` rather than `instanceof Object`.",
|
||||
},
|
||||
{
|
||||
selector: "MemberExpression[property.name='hasOwnProperty']",
|
||||
message:
|
||||
"Use `Object.hasOwn` rather than `Object.prototype.hasOwnProperty`.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='assert'][arguments.length!=2]",
|
||||
message: "`assert()` must always be invoked with two arguments.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='isCmd'][arguments.length<2]",
|
||||
message:
|
||||
"Use `instanceof Cmd` rather than `isCmd()` with one argument.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='isDict'][arguments.length<2]",
|
||||
message:
|
||||
"Use `instanceof Dict` rather than `isDict()` with one argument.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='isName'][arguments.length<2]",
|
||||
message:
|
||||
"Use `instanceof Name` rather than `isName()` with one argument.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name='Cmd']",
|
||||
message: "Use `Cmd.get()` rather than `new Cmd()`.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name='Name']",
|
||||
message: "Use `Name.get()` rather than `new Name()`.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name='ObjectLoader']",
|
||||
message:
|
||||
"Use `ObjectLoader.load()` rather than `new ObjectLoader()`.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name='Ref']",
|
||||
message: "Use `Ref.get()` rather than `new Ref()`.",
|
||||
},
|
||||
{
|
||||
selector: "ExportNamedDeclaration[declaration]",
|
||||
message:
|
||||
"Separate the declaration and the export statement, using `export { ... }`.",
|
||||
},
|
||||
{
|
||||
selector: "ExportDefaultDeclaration:has(> :declaration)",
|
||||
message:
|
||||
"Separate the declaration and the export statement, using `export default <variable name>`.",
|
||||
},
|
||||
],
|
||||
"no-unneeded-ternary": "error",
|
||||
"operator-assignment": "error",
|
||||
"prefer-exponentiation-operator": "error",
|
||||
"spaced-comment": ["error", "always", { block: { balanced: true } }],
|
||||
|
||||
// ECMAScript 6
|
||||
"arrow-body-style": ["error", "as-needed"],
|
||||
"constructor-super": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-var": "error",
|
||||
"object-shorthand": ["error", "always", { avoidQuotes: true }],
|
||||
"prefer-const": "error",
|
||||
"require-yield": "error",
|
||||
"sort-imports": ["error", { ignoreCase: true }],
|
||||
"template-curly-spacing": ["error", "never"],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: jsFiles("src"),
|
||||
rules: {
|
||||
"no-console": "error",
|
||||
},
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
Test-specific rules
|
||||
\* ======================================================================== */
|
||||
|
||||
{
|
||||
files: jsFiles("test"),
|
||||
|
||||
plugins: { jasmine },
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jasmine,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...jasmine.configs.recommended.rules,
|
||||
"jasmine/new-line-before-expect": "off",
|
||||
"jasmine/new-line-between-declarations": "off",
|
||||
"jasmine/no-focused-tests": "error",
|
||||
"jasmine/no-pending-tests": "off",
|
||||
"jasmine/no-spec-dupes": ["error", "branch"],
|
||||
"jasmine/no-suite-dupes": ["error", "branch"],
|
||||
"jasmine/prefer-jasmine-matcher": "off",
|
||||
"jasmine/prefer-toHaveBeenCalledWith": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: jsFiles("test/unit"),
|
||||
rules: {
|
||||
"import/no-unresolved": ["error", { ignore: ["pdfjs/"] }],
|
||||
"no-console": ["error", { allow: ["warn", "error"] }],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: jsFiles("test/integration"),
|
||||
rules: {
|
||||
"no-console": ["error", { allow: ["warn", "error"] }],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "CallExpression[callee.name='waitForTimeout']",
|
||||
message:
|
||||
"`waitForTimeout` can cause intermittent failures and should not be used (see issue #17656 for replacements).",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
External libraries
|
||||
\* ======================================================================== */
|
||||
|
||||
{
|
||||
files: jsFiles("external"),
|
||||
|
||||
languageOptions: { globals: globals.node },
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
Examples
|
||||
\* ======================================================================== */
|
||||
|
||||
{
|
||||
files: jsFiles("examples"),
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
pdfjsImageDecoders: false,
|
||||
pdfjsLib: false,
|
||||
pdfjsViewer: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [...jsFiles("examples/node"), ...jsFiles("examples/webpack")],
|
||||
|
||||
languageOptions: { globals: globals.node },
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
Chromium extension
|
||||
\* ======================================================================== */
|
||||
|
||||
{
|
||||
files: jsFiles("extensions/chromium"),
|
||||
|
||||
languageOptions: {
|
||||
globals: globals.webextensions,
|
||||
sourceType: "script",
|
||||
},
|
||||
|
||||
rules: {
|
||||
"no-var": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: chromiumExtensionServiceWorkerFiles,
|
||||
|
||||
languageOptions: {
|
||||
globals: globals.serviceworker,
|
||||
sourceType: "script",
|
||||
},
|
||||
},
|
||||
|
||||
/* ======================================================================== *\
|
||||
Other
|
||||
\* ======================================================================== */
|
||||
{
|
||||
files: ["gulpfile.mjs"],
|
||||
languageOptions: { globals: globals.node },
|
||||
},
|
||||
];
|
||||
43
examples/components/pageviewer.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2014 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html dir="ltr" mozdisallowselectionprint>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<title>PDF.js page viewer using built components</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #808080;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body tabindex="1">
|
||||
<div id="pageContainer" class="pdfViewer singlePageView"></div>
|
||||
|
||||
<script src="pageviewer.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
63
examples/components/pageviewer.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!pdfjsLib.getDocument || !pdfjsViewer.PDFPageView) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
||||
}
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
// Some PDFs need external cmaps.
|
||||
//
|
||||
const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
||||
const CMAP_PACKED = true;
|
||||
|
||||
const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
const PAGE_TO_VIEW = 1;
|
||||
const SCALE = 1.0;
|
||||
|
||||
const ENABLE_XFA = true;
|
||||
|
||||
const container = document.getElementById("pageContainer");
|
||||
|
||||
const eventBus = new pdfjsViewer.EventBus();
|
||||
|
||||
// Loading document.
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url: DEFAULT_URL,
|
||||
cMapUrl: CMAP_URL,
|
||||
cMapPacked: CMAP_PACKED,
|
||||
enableXfa: ENABLE_XFA,
|
||||
});
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Document loaded, retrieving the page.
|
||||
const pdfPage = await pdfDocument.getPage(PAGE_TO_VIEW);
|
||||
|
||||
// Creating the page view with default parameters.
|
||||
const pdfPageView = new pdfjsViewer.PDFPageView({
|
||||
container,
|
||||
id: PAGE_TO_VIEW,
|
||||
scale: SCALE,
|
||||
defaultViewport: pdfPage.getViewport({ scale: SCALE }),
|
||||
eventBus,
|
||||
});
|
||||
// Associate the actual page with the view, and draw it.
|
||||
pdfPageView.setPdfPage(pdfPage);
|
||||
pdfPageView.draw();
|
||||
51
examples/components/simpleviewer.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2014 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html dir="ltr" mozdisallowselectionprint>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<title>PDF.js viewer using built components</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #808080;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#viewerContainer {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body tabindex="1">
|
||||
<div id="viewerContainer">
|
||||
<div id="viewer" class="pdfViewer"></div>
|
||||
</div>
|
||||
|
||||
<script src="simpleviewer.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
100
examples/components/simpleviewer.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
||||
}
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
// Some PDFs need external cmaps.
|
||||
//
|
||||
const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
||||
const CMAP_PACKED = true;
|
||||
|
||||
const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
// To test the AcroForm and/or scripting functionality, try e.g. this file:
|
||||
// "../../test/pdfs/160F-2019.pdf"
|
||||
|
||||
const ENABLE_XFA = true;
|
||||
const SEARCH_FOR = ""; // try "Mozilla";
|
||||
|
||||
const SANDBOX_BUNDLE_SRC = new URL(
|
||||
"../../node_modules/pdfjs-dist/build/pdf.sandbox.mjs",
|
||||
window.location
|
||||
);
|
||||
|
||||
const WASM_URL = "../../node_modules/pdfjs-dist/build/wasm/";
|
||||
|
||||
const container = document.getElementById("viewerContainer");
|
||||
|
||||
const eventBus = new pdfjsViewer.EventBus();
|
||||
|
||||
// (Optionally) enable hyperlinks within PDF files.
|
||||
const pdfLinkService = new pdfjsViewer.PDFLinkService({
|
||||
eventBus,
|
||||
});
|
||||
|
||||
// (Optionally) enable find controller.
|
||||
const pdfFindController = new pdfjsViewer.PDFFindController({
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
});
|
||||
|
||||
// (Optionally) enable scripting support.
|
||||
const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({
|
||||
eventBus,
|
||||
sandboxBundleSrc: SANDBOX_BUNDLE_SRC,
|
||||
wasmUrl: WASM_URL,
|
||||
});
|
||||
|
||||
const pdfViewer = new pdfjsViewer.PDFViewer({
|
||||
container,
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
findController: pdfFindController,
|
||||
scriptingManager: pdfScriptingManager,
|
||||
});
|
||||
pdfLinkService.setViewer(pdfViewer);
|
||||
pdfScriptingManager.setViewer(pdfViewer);
|
||||
|
||||
eventBus.on("pagesinit", function () {
|
||||
// We can use pdfViewer now, e.g. let's change default scale.
|
||||
pdfViewer.currentScaleValue = "page-width";
|
||||
|
||||
// We can try searching for things.
|
||||
if (SEARCH_FOR) {
|
||||
eventBus.dispatch("find", { type: "", query: SEARCH_FOR });
|
||||
}
|
||||
});
|
||||
|
||||
// Loading document.
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url: DEFAULT_URL,
|
||||
cMapUrl: CMAP_URL,
|
||||
cMapPacked: CMAP_PACKED,
|
||||
enableXfa: ENABLE_XFA,
|
||||
});
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Document loaded, specifying document for the viewer and
|
||||
// the (optional) linkService.
|
||||
pdfViewer.setDocument(pdfDocument);
|
||||
|
||||
pdfLinkService.setDocument(pdfDocument, null);
|
||||
51
examples/components/singlepageviewer.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2014 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html dir="ltr" mozdisallowselectionprint>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<title>PDF.js Single Page Viewer using built components</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #808080;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#viewerContainer {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body tabindex="1">
|
||||
<div id="viewerContainer">
|
||||
<div id="viewer" class="pdfViewer"></div>
|
||||
</div>
|
||||
|
||||
<script src="singlepageviewer.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
100
examples/components/singlepageviewer.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!pdfjsLib.getDocument || !pdfjsViewer.PDFSinglePageViewer) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
||||
}
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
// Some PDFs need external cmaps.
|
||||
//
|
||||
const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
||||
const CMAP_PACKED = true;
|
||||
|
||||
const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
// To test the AcroForm and/or scripting functionality, try e.g. this file:
|
||||
// "../../test/pdfs/160F-2019.pdf"
|
||||
|
||||
const ENABLE_XFA = true;
|
||||
const SEARCH_FOR = ""; // try "Mozilla";
|
||||
|
||||
const SANDBOX_BUNDLE_SRC = new URL(
|
||||
"../../node_modules/pdfjs-dist/build/pdf.sandbox.mjs",
|
||||
window.location
|
||||
);
|
||||
|
||||
const WASM_URL = "../../node_modules/pdfjs-dist/build/wasm/";
|
||||
|
||||
const container = document.getElementById("viewerContainer");
|
||||
|
||||
const eventBus = new pdfjsViewer.EventBus();
|
||||
|
||||
// (Optionally) enable hyperlinks within PDF files.
|
||||
const pdfLinkService = new pdfjsViewer.PDFLinkService({
|
||||
eventBus,
|
||||
});
|
||||
|
||||
// (Optionally) enable find controller.
|
||||
const pdfFindController = new pdfjsViewer.PDFFindController({
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
});
|
||||
|
||||
// (Optionally) enable scripting support.
|
||||
const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({
|
||||
eventBus,
|
||||
sandboxBundleSrc: SANDBOX_BUNDLE_SRC,
|
||||
wasmUrl: WASM_URL,
|
||||
});
|
||||
|
||||
const pdfSinglePageViewer = new pdfjsViewer.PDFSinglePageViewer({
|
||||
container,
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
findController: pdfFindController,
|
||||
scriptingManager: pdfScriptingManager,
|
||||
});
|
||||
pdfLinkService.setViewer(pdfSinglePageViewer);
|
||||
pdfScriptingManager.setViewer(pdfSinglePageViewer);
|
||||
|
||||
eventBus.on("pagesinit", function () {
|
||||
// We can use pdfSinglePageViewer now, e.g. let's change default scale.
|
||||
pdfSinglePageViewer.currentScaleValue = "page-width";
|
||||
|
||||
// We can try searching for things.
|
||||
if (SEARCH_FOR) {
|
||||
eventBus.dispatch("find", { type: "", query: SEARCH_FOR });
|
||||
}
|
||||
});
|
||||
|
||||
// Loading document.
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url: DEFAULT_URL,
|
||||
cMapUrl: CMAP_URL,
|
||||
cMapPacked: CMAP_PACKED,
|
||||
enableXfa: ENABLE_XFA,
|
||||
});
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Document loaded, specifying document for the viewer and
|
||||
// the (optional) linkService.
|
||||
pdfSinglePageViewer.setDocument(pdfDocument);
|
||||
|
||||
pdfLinkService.setDocument(pdfDocument, null);
|
||||
BIN
examples/image_decoders/fish.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
40
examples/image_decoders/jpeg_viewer.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2018 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html dir="ltr" mozdisallowselectionprint>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<title>PDF.js standalone JpegImage parser</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #808080;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/image_decoders/pdf.image_decoders.mjs" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body tabindex="1">
|
||||
<canvas id="jpegCanvas" width="0" height="0"></canvas>
|
||||
|
||||
<script src="jpeg_viewer.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
59
examples/image_decoders/jpeg_viewer.mjs
Normal file
@@ -0,0 +1,59 @@
|
||||
/* Copyright 2018 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!pdfjsImageDecoders.JpegImage) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using `gulp dist-install`");
|
||||
}
|
||||
|
||||
const JPEG_IMAGE = "fish.jpg";
|
||||
|
||||
const jpegCanvas = document.getElementById("jpegCanvas");
|
||||
const jpegCtx = jpegCanvas.getContext("2d");
|
||||
|
||||
// Load the image data, and convert it to a Uint8Array.
|
||||
//
|
||||
const response = await fetch(JPEG_IMAGE);
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
const typedArrayImage = await response.bytes();
|
||||
|
||||
// Parse the image data using `JpegImage`.
|
||||
//
|
||||
const jpegImage = new pdfjsImageDecoders.JpegImage();
|
||||
jpegImage.parse(typedArrayImage);
|
||||
|
||||
const width = jpegImage.width,
|
||||
height = jpegImage.height;
|
||||
const jpegData = jpegImage.getData({
|
||||
width,
|
||||
height,
|
||||
forceRGB: true,
|
||||
});
|
||||
|
||||
// Render the JPEG image on a <canvas>.
|
||||
//
|
||||
const imageData = jpegCtx.createImageData(width, height);
|
||||
const imageBytes = imageData.data;
|
||||
for (let j = 0, k = 0, jj = width * height * 4; j < jj; ) {
|
||||
imageBytes[j++] = jpegData[k++];
|
||||
imageBytes[j++] = jpegData[k++];
|
||||
imageBytes[j++] = jpegData[k++];
|
||||
imageBytes[j++] = 255;
|
||||
}
|
||||
jpegCanvas.width = width;
|
||||
jpegCanvas.height = height;
|
||||
jpegCtx.putImageData(imageData, 0, 0);
|
||||
71
examples/learning/helloworld.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>'Hello, world!' example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>'Hello, world!' example</h1>
|
||||
|
||||
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
|
||||
<script id="script" type="module">
|
||||
//
|
||||
// If absolute URL from the remote server is provided, configure the CORS
|
||||
// header on that server.
|
||||
//
|
||||
const url = "./helloworld.pdf";
|
||||
|
||||
//
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
//
|
||||
// Asynchronous download PDF
|
||||
//
|
||||
const loadingTask = pdfjsLib.getDocument({ url });
|
||||
const pdf = await loadingTask.promise;
|
||||
//
|
||||
// Fetch the first page
|
||||
//
|
||||
const page = await pdf.getPage(1);
|
||||
const scale = 1.5;
|
||||
const viewport = page.getViewport({ scale });
|
||||
// Support HiDPI-screens.
|
||||
const outputScale = window.devicePixelRatio || 1;
|
||||
|
||||
//
|
||||
// Prepare canvas using PDF page dimensions
|
||||
//
|
||||
const canvas = document.getElementById("the-canvas");
|
||||
const context = 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;
|
||||
|
||||
//
|
||||
// Render PDF page into canvas context
|
||||
//
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
transform,
|
||||
viewport,
|
||||
};
|
||||
page.render(renderContext);
|
||||
</script>
|
||||
|
||||
<hr />
|
||||
<h2>JavaScript code:</h2>
|
||||
<pre id="code"></pre>
|
||||
<script>
|
||||
document.getElementById("code").textContent = document.getElementById("script").text;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
68
examples/learning/helloworld.pdf
Normal file
@@ -0,0 +1,68 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj % entry point
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/MediaBox [ 0 0 200 200 ]
|
||||
/Count 1
|
||||
/Kids [ 3 0 R ]
|
||||
>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 4 0 R
|
||||
>>
|
||||
>>
|
||||
/Contents 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type1
|
||||
/BaseFont /Times-Roman
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj % page content
|
||||
<<
|
||||
/Length 44
|
||||
>>
|
||||
stream
|
||||
BT
|
||||
70 50 TD
|
||||
/F1 12 Tf
|
||||
(Hello, world!) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000079 00000 n
|
||||
0000000173 00000 n
|
||||
0000000301 00000 n
|
||||
0000000380 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Size 6
|
||||
/Root 1 0 R
|
||||
>>
|
||||
startxref
|
||||
492
|
||||
%%EOF
|
||||
77
examples/learning/helloworld64.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>'Hello, world!' base64 example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>'Hello, world!' example</h1>
|
||||
|
||||
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
|
||||
<script id="script" type="module">
|
||||
// atob() is used to convert base64 encoded PDF to binary-like data.
|
||||
// (See also https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/
|
||||
// Base64_encoding_and_decoding.)
|
||||
var pdfData = atob(
|
||||
"JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog" +
|
||||
"IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv" +
|
||||
"TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K" +
|
||||
"Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg" +
|
||||
"L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+" +
|
||||
"PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u" +
|
||||
"dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq" +
|
||||
"Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU" +
|
||||
"CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu" +
|
||||
"ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g" +
|
||||
"CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw" +
|
||||
"MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v" +
|
||||
"dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G"
|
||||
);
|
||||
|
||||
//
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
// Opening PDF by passing its binary data as a string. It is still preferable
|
||||
// to use Uint8Array, but string or array-like structure will work too.
|
||||
var loadingTask = pdfjsLib.getDocument({ data: pdfData });
|
||||
var pdf = await loadingTask.promise;
|
||||
// Fetch the first page.
|
||||
var page = await pdf.getPage(1);
|
||||
var scale = 1.5;
|
||||
var viewport = page.getViewport({ scale: scale });
|
||||
// Support HiDPI-screens.
|
||||
var outputScale = window.devicePixelRatio || 1;
|
||||
|
||||
// Prepare canvas using PDF page dimensions.
|
||||
var canvas = document.getElementById("the-canvas");
|
||||
var context = 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";
|
||||
|
||||
var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
|
||||
|
||||
// Render PDF page into canvas context.
|
||||
var renderContext = {
|
||||
canvasContext: context,
|
||||
transform,
|
||||
viewport,
|
||||
};
|
||||
page.render(renderContext);
|
||||
</script>
|
||||
|
||||
<hr />
|
||||
<h2>JavaScript code:</h2>
|
||||
<pre id="code"></pre>
|
||||
<script>
|
||||
document.getElementById("code").textContent = document.getElementById("script").text;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
134
examples/learning/prevnext.html
Normal file
@@ -0,0 +1,134 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Previous/Next example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>'Previous/Next' example</h1>
|
||||
|
||||
<div>
|
||||
<button id="prev" type="button">Previous</button>
|
||||
<button id="next" type="button">Next</button>
|
||||
|
||||
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
|
||||
<script id="script" type="module">
|
||||
//
|
||||
// If absolute URL from the remote server is provided, configure the CORS
|
||||
// header on that server.
|
||||
//
|
||||
var url = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
|
||||
//
|
||||
// In cases when the pdf.worker.js is located at the different folder than the
|
||||
// PDF.js's one, or the PDF.js is executed via eval(), the workerSrc property
|
||||
// shall be specified.
|
||||
//
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
var pdfDoc = null,
|
||||
pageNum = 1,
|
||||
pageRendering = false,
|
||||
pageNumPending = null,
|
||||
scale = 0.8,
|
||||
canvas = document.getElementById("the-canvas"),
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
/**
|
||||
* Get page info from document, resize canvas accordingly, and render page.
|
||||
* @param num Page number.
|
||||
*/
|
||||
function renderPage(num) {
|
||||
pageRendering = true;
|
||||
// Using promise to fetch the page
|
||||
pdfDoc.getPage(num).then(function (page) {
|
||||
var viewport = page.getViewport({ scale: scale });
|
||||
// Support HiDPI-screens.
|
||||
var outputScale = window.devicePixelRatio || 1;
|
||||
|
||||
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";
|
||||
|
||||
var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
|
||||
|
||||
// Render PDF page into canvas context
|
||||
var renderContext = {
|
||||
canvasContext: ctx,
|
||||
transform: transform,
|
||||
viewport: viewport,
|
||||
};
|
||||
var renderTask = page.render(renderContext);
|
||||
|
||||
// Wait for rendering to finish
|
||||
renderTask.promise.then(function () {
|
||||
pageRendering = false;
|
||||
if (pageNumPending !== null) {
|
||||
// New page rendering is pending
|
||||
renderPage(pageNumPending);
|
||||
pageNumPending = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update page counters
|
||||
document.getElementById("page_num").textContent = num;
|
||||
}
|
||||
|
||||
/**
|
||||
* If another page rendering in progress, waits until the rendering is
|
||||
* finished. Otherwise, executes rendering immediately.
|
||||
*/
|
||||
function queueRenderPage(num) {
|
||||
if (pageRendering) {
|
||||
pageNumPending = num;
|
||||
} else {
|
||||
renderPage(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays previous page.
|
||||
*/
|
||||
function onPrevPage() {
|
||||
if (pageNum <= 1) {
|
||||
return;
|
||||
}
|
||||
pageNum--;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById("prev").addEventListener("click", onPrevPage);
|
||||
|
||||
/**
|
||||
* Displays next page.
|
||||
*/
|
||||
function onNextPage() {
|
||||
if (pageNum >= pdfDoc.numPages) {
|
||||
return;
|
||||
}
|
||||
pageNum++;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById("next").addEventListener("click", onNextPage);
|
||||
|
||||
/**
|
||||
* Asynchronously downloads PDF.
|
||||
*/
|
||||
var loadingTask = pdfjsLib.getDocument({ url });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
document.getElementById("page_count").textContent = pdfDoc.numPages;
|
||||
|
||||
// Initial/first page rendering
|
||||
renderPage(pageNum);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
11
examples/mobile-viewer/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## Overview
|
||||
|
||||
Example to demonstrate PDF.js library usage with a viewer optimized for mobile usage.
|
||||
|
||||
## Getting started
|
||||
|
||||
Build PDF.js using `gulp dist-install` and run `gulp server` to start a web server.
|
||||
You can then work with the mobile viewer at
|
||||
http://localhost:8888/examples/mobile-viewer/viewer.html.
|
||||
|
||||
Refer to `viewer.js` for the source code of the mobile viewer.
|
||||
BIN
examples/mobile-viewer/images/div_line_left.png
Normal file
|
After Width: | Height: | Size: 169 B |
BIN
examples/mobile-viewer/images/div_line_left@1.5x.png
Normal file
|
After Width: | Height: | Size: 185 B |
BIN
examples/mobile-viewer/images/div_line_left@2x.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
examples/mobile-viewer/images/div_line_right.png
Normal file
|
After Width: | Height: | Size: 166 B |
BIN
examples/mobile-viewer/images/div_line_right@1.5x.png
Normal file
|
After Width: | Height: | Size: 184 B |
BIN
examples/mobile-viewer/images/div_line_right@2x.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
examples/mobile-viewer/images/document_bg.png
Normal file
|
After Width: | Height: | Size: 560 B |
BIN
examples/mobile-viewer/images/icon_next_page.png
Normal file
|
After Width: | Height: | Size: 310 B |
BIN
examples/mobile-viewer/images/icon_next_page@1.5x.png
Normal file
|
After Width: | Height: | Size: 338 B |
BIN
examples/mobile-viewer/images/icon_previous_page.png
Normal file
|
After Width: | Height: | Size: 372 B |
BIN
examples/mobile-viewer/images/icon_previous_page@1.5x.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
examples/mobile-viewer/images/icon_zoom_in.png
Normal file
|
After Width: | Height: | Size: 640 B |
BIN
examples/mobile-viewer/images/icon_zoom_in@1.5x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
examples/mobile-viewer/images/icon_zoom_out.png
Normal file
|
After Width: | Height: | Size: 564 B |
BIN
examples/mobile-viewer/images/icon_zoom_out@1.5x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
examples/mobile-viewer/images/spinner.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
examples/mobile-viewer/images/toolbar_background.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
242
examples/mobile-viewer/viewer.css
Normal file
@@ -0,0 +1,242 @@
|
||||
/* Copyright 2016 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: rgb(244 244 244 / 1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
border-bottom: 1px solid rgb(216 216 216 / 1);
|
||||
color: rgb(133 133 133 / 1);
|
||||
font-size: 23px;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body {
|
||||
background: url(images/document_bg.png);
|
||||
color: rgb(255 255 255 / 1);
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url(images/toolbar_background.png);
|
||||
height: 4rem;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
box-shadow: 0 -0.2rem 0.5rem rgb(50 50 50 / 0.75);
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-width: 0;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgb(0 0 0 / 0);
|
||||
}
|
||||
|
||||
.toolbarButton.pageUp {
|
||||
position: absolute;
|
||||
width: 18%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
background-image: url(images/icon_previous_page.png);
|
||||
background-size: 2rem;
|
||||
}
|
||||
|
||||
.toolbarButton.pageDown {
|
||||
position: absolute;
|
||||
width: 18%;
|
||||
height: 100%;
|
||||
left: 18%;
|
||||
background-image: url(images/icon_next_page.png);
|
||||
background-size: 2rem;
|
||||
}
|
||||
|
||||
#pageNumber {
|
||||
-moz-appearance: textfield; /* hides the spinner in moz */
|
||||
position: absolute;
|
||||
width: 28%;
|
||||
height: 100%;
|
||||
left: 36%;
|
||||
text-align: center;
|
||||
border: 0;
|
||||
background-color: rgb(0 0 0 / 0);
|
||||
font-size: 1.2rem;
|
||||
color: rgb(255 255 255 / 1);
|
||||
background-image:
|
||||
url(images/div_line_left.png), url(images/div_line_right.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: left, right;
|
||||
background-size: 0.2rem, 0.2rem;
|
||||
}
|
||||
|
||||
.toolbarButton.zoomOut {
|
||||
position: absolute;
|
||||
width: 18%;
|
||||
height: 100%;
|
||||
left: 64%;
|
||||
background-image: url(images/icon_zoom_out.png);
|
||||
background-size: 2.4rem;
|
||||
}
|
||||
|
||||
.toolbarButton.zoomIn {
|
||||
position: absolute;
|
||||
width: 18%;
|
||||
height: 100%;
|
||||
left: 82%;
|
||||
background-image: url(images/icon_zoom_in.png);
|
||||
background-size: 2.4rem;
|
||||
}
|
||||
|
||||
.toolbarButton[disabled] {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#viewerContainer {
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
inset: 5rem 0 4rem;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pdfViewer .page .loadingIcon {
|
||||
width: 2.9rem;
|
||||
height: 2.9rem;
|
||||
background: url("images/spinner.png") no-repeat left top / 38rem;
|
||||
border: medium none;
|
||||
animation: 1s steps(10, end) 0s normal none infinite moveDefault;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: calc((100% - 2.9rem) / 2);
|
||||
left: calc((100% - 2.9rem) / 2);
|
||||
}
|
||||
|
||||
@keyframes moveDefault {
|
||||
from {
|
||||
background-position: 0 top;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: -39rem top;
|
||||
}
|
||||
}
|
||||
|
||||
#loadingBar {
|
||||
/* Define this variable here, and not in :root, to avoid reflowing the
|
||||
entire viewer when updating progress (see issue 15958). */
|
||||
--progressBar-percent: 0%;
|
||||
|
||||
position: relative;
|
||||
height: 0.6rem;
|
||||
background-color: rgb(51 51 51 / 1);
|
||||
border-bottom: 1px solid rgb(51 51 51 / 1);
|
||||
}
|
||||
|
||||
#loadingBar .progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transform: scaleX(var(--progressBar-percent));
|
||||
transform-origin: 0 0;
|
||||
height: 100%;
|
||||
background-color: rgb(221 221 221 / 1);
|
||||
overflow: hidden;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
@keyframes progressIndeterminate {
|
||||
0% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
#loadingBar.indeterminate .progress {
|
||||
transform: none;
|
||||
background-color: rgb(153 153 153 / 1);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#loadingBar.indeterminate .progress .glimmer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 5rem;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgb(153 153 153 / 1) 0%,
|
||||
rgb(255 255 255 / 1) 50%,
|
||||
rgb(153 153 153 / 1) 100%
|
||||
);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: progressIndeterminate 2s linear infinite;
|
||||
}
|
||||
57
examples/mobile-viewer/viewer.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2016 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
|
||||
<title>PDF.js viewer</title>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
|
||||
<link rel="stylesheet" type="text/css" href="viewer.css" />
|
||||
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1 id="title"></h1>
|
||||
</header>
|
||||
|
||||
<div id="viewerContainer">
|
||||
<div id="viewer" class="pdfViewer"></div>
|
||||
</div>
|
||||
|
||||
<div id="loadingBar">
|
||||
<div class="progress"></div>
|
||||
<div class="glimmer"></div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button class="toolbarButton pageUp" title="Previous Page" id="previous" type="button"></button>
|
||||
<button class="toolbarButton pageDown" title="Next Page" id="next" type="button"></button>
|
||||
|
||||
<input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" />
|
||||
|
||||
<button class="toolbarButton zoomOut" title="Zoom Out" id="zoomOut" type="button"></button>
|
||||
<button class="toolbarButton zoomIn" title="Zoom In" id="zoomIn" type="button"></button>
|
||||
</footer>
|
||||
|
||||
<script src="viewer.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
347
examples/mobile-viewer/viewer.mjs
Normal file
@@ -0,0 +1,347 @@
|
||||
/* Copyright 2016 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
||||
}
|
||||
|
||||
const MAX_CANVAS_PIXELS = 0; // CSS-only zooming.
|
||||
const TEXT_LAYER_MODE = 0; // DISABLE
|
||||
const MAX_IMAGE_SIZE = 1024 * 1024;
|
||||
const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
||||
const CMAP_PACKED = true;
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
const DEFAULT_SCALE_DELTA = 1.1;
|
||||
const MIN_SCALE = 0.25;
|
||||
const MAX_SCALE = 10.0;
|
||||
const DEFAULT_SCALE_VALUE = "auto";
|
||||
|
||||
const PDFViewerApplication = {
|
||||
pdfLoadingTask: null,
|
||||
pdfDocument: null,
|
||||
pdfViewer: null,
|
||||
pdfHistory: null,
|
||||
pdfLinkService: null,
|
||||
eventBus: null,
|
||||
|
||||
/**
|
||||
* Opens PDF document specified by URL.
|
||||
* @returns {Promise} - Returns the promise, which is resolved when document
|
||||
* is opened.
|
||||
*/
|
||||
async open(params) {
|
||||
if (this.pdfLoadingTask) {
|
||||
// We need to destroy already opened document.
|
||||
await this.close();
|
||||
}
|
||||
|
||||
const { url } = params;
|
||||
this.setTitleUsingUrl(url);
|
||||
|
||||
// Loading document.
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url,
|
||||
maxImageSize: MAX_IMAGE_SIZE,
|
||||
cMapUrl: CMAP_URL,
|
||||
cMapPacked: CMAP_PACKED,
|
||||
});
|
||||
this.pdfLoadingTask = loadingTask;
|
||||
|
||||
loadingTask.onProgress = evt => this.progress(evt.percent);
|
||||
|
||||
return loadingTask.promise.then(
|
||||
pdfDocument => {
|
||||
// Document loaded, specifying document for the viewer.
|
||||
this.pdfDocument = pdfDocument;
|
||||
this.pdfViewer.setDocument(pdfDocument);
|
||||
this.pdfLinkService.setDocument(pdfDocument);
|
||||
this.pdfHistory.initialize({
|
||||
fingerprint: pdfDocument.fingerprints[0],
|
||||
});
|
||||
|
||||
this.loadingBar.hide();
|
||||
this.setTitleUsingMetadata(pdfDocument);
|
||||
},
|
||||
reason => {
|
||||
let key = "pdfjs-loading-error";
|
||||
if (reason instanceof pdfjsLib.InvalidPDFException) {
|
||||
key = "pdfjs-invalid-file-error";
|
||||
} else if (reason instanceof pdfjsLib.ResponseException) {
|
||||
key = reason.missing
|
||||
? "pdfjs-missing-file-error"
|
||||
: "pdfjs-unexpected-response-error";
|
||||
}
|
||||
this.l10n.get(key).then(msg => {
|
||||
this.error(msg, { message: reason.message });
|
||||
});
|
||||
this.loadingBar.hide();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes opened PDF document.
|
||||
* @returns {Promise} - Returns the promise, which is resolved when all
|
||||
* destruction is completed.
|
||||
*/
|
||||
async close() {
|
||||
if (!this.pdfLoadingTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promise = this.pdfLoadingTask.destroy();
|
||||
this.pdfLoadingTask = null;
|
||||
|
||||
if (this.pdfDocument) {
|
||||
this.pdfDocument = null;
|
||||
|
||||
this.pdfViewer.setDocument(null);
|
||||
this.pdfLinkService.setDocument(null, null);
|
||||
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.reset();
|
||||
}
|
||||
}
|
||||
|
||||
await promise;
|
||||
},
|
||||
|
||||
get loadingBar() {
|
||||
const bar = document.getElementById("loadingBar");
|
||||
return pdfjsLib.shadow(
|
||||
this,
|
||||
"loadingBar",
|
||||
new pdfjsViewer.ProgressBar(bar)
|
||||
);
|
||||
},
|
||||
|
||||
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
|
||||
this.url = url;
|
||||
let title = pdfjsLib.getFilenameFromUrl(url) || url;
|
||||
try {
|
||||
title = decodeURIComponent(title);
|
||||
} catch {
|
||||
// decodeURIComponent may throw URIError,
|
||||
// fall back to using the unprocessed url in that case
|
||||
}
|
||||
this.setTitle(title);
|
||||
},
|
||||
|
||||
async setTitleUsingMetadata(pdfDocument) {
|
||||
const { info, metadata } = await pdfDocument.getMetadata();
|
||||
this.documentInfo = info;
|
||||
this.metadata = metadata;
|
||||
|
||||
// Provides some basic debug information
|
||||
console.log(
|
||||
`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
|
||||
`${(metadata?.get("pdf:producer") || info.Producer || "-").trim()} / ` +
|
||||
`${(metadata?.get("xmp:creatortool") || info.Creator || "-").trim()}` +
|
||||
`] (PDF.js: ${pdfjsLib.version || "?"} [${pdfjsLib.build || "?"}])`
|
||||
);
|
||||
|
||||
let pdfTitle;
|
||||
if (metadata?.has("dc:title")) {
|
||||
const title = metadata.get("dc:title");
|
||||
// Ghostscript sometimes returns 'Untitled', so prevent setting the
|
||||
// title to 'Untitled.
|
||||
if (title !== "Untitled") {
|
||||
pdfTitle = title;
|
||||
}
|
||||
}
|
||||
pdfTitle ||= info?.Title;
|
||||
|
||||
if (pdfTitle) {
|
||||
this.setTitle(pdfTitle + " - " + document.title);
|
||||
}
|
||||
},
|
||||
|
||||
setTitle: function pdfViewSetTitle(title) {
|
||||
document.title = title;
|
||||
document.getElementById("title").textContent = title;
|
||||
},
|
||||
|
||||
error: function pdfViewError(message, moreInfo) {
|
||||
const moreInfoText = [
|
||||
`PDF.js v${pdfjsLib.version || "?"} (build: ${pdfjsLib.build || "?"})`,
|
||||
];
|
||||
if (moreInfo) {
|
||||
moreInfoText.push(`Message: ${moreInfo.message}`);
|
||||
|
||||
if (moreInfo.stack) {
|
||||
moreInfoText.push(`Stack: ${moreInfo.stack}`);
|
||||
} else {
|
||||
if (moreInfo.filename) {
|
||||
moreInfoText.push(`File: ${moreInfo.filename}`);
|
||||
}
|
||||
if (moreInfo.lineNumber) {
|
||||
moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
|
||||
},
|
||||
|
||||
progress(percent) {
|
||||
// Updating the bar if value increases.
|
||||
if (percent > this.loadingBar.percent || isNaN(percent)) {
|
||||
this.loadingBar.percent = percent;
|
||||
}
|
||||
},
|
||||
|
||||
get pagesCount() {
|
||||
return this.pdfDocument.numPages;
|
||||
},
|
||||
|
||||
get page() {
|
||||
return this.pdfViewer.currentPageNumber;
|
||||
},
|
||||
|
||||
set page(val) {
|
||||
this.pdfViewer.currentPageNumber = val;
|
||||
},
|
||||
|
||||
zoomIn: function pdfViewZoomIn(ticks) {
|
||||
let newScale = this.pdfViewer.currentScale;
|
||||
do {
|
||||
newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
|
||||
newScale = Math.ceil(newScale * 10) / 10;
|
||||
newScale = Math.min(MAX_SCALE, newScale);
|
||||
} while (--ticks && newScale < MAX_SCALE);
|
||||
this.pdfViewer.currentScaleValue = newScale;
|
||||
},
|
||||
|
||||
zoomOut: function pdfViewZoomOut(ticks) {
|
||||
let newScale = this.pdfViewer.currentScale;
|
||||
do {
|
||||
newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
|
||||
newScale = Math.floor(newScale * 10) / 10;
|
||||
newScale = Math.max(MIN_SCALE, newScale);
|
||||
} while (--ticks && newScale > MIN_SCALE);
|
||||
this.pdfViewer.currentScaleValue = newScale;
|
||||
},
|
||||
|
||||
initUI: function pdfViewInitUI() {
|
||||
const eventBus = new pdfjsViewer.EventBus();
|
||||
this.eventBus = eventBus;
|
||||
|
||||
const linkService = new pdfjsViewer.PDFLinkService({
|
||||
eventBus,
|
||||
});
|
||||
this.pdfLinkService = linkService;
|
||||
|
||||
this.l10n = new pdfjsViewer.GenericL10n();
|
||||
|
||||
const container = document.getElementById("viewerContainer");
|
||||
const pdfViewer = new pdfjsViewer.PDFViewer({
|
||||
container,
|
||||
eventBus,
|
||||
linkService,
|
||||
l10n: this.l10n,
|
||||
maxCanvasPixels: MAX_CANVAS_PIXELS,
|
||||
textLayerMode: TEXT_LAYER_MODE,
|
||||
});
|
||||
this.pdfViewer = pdfViewer;
|
||||
linkService.setViewer(pdfViewer);
|
||||
|
||||
this.pdfHistory = new pdfjsViewer.PDFHistory({
|
||||
eventBus,
|
||||
linkService,
|
||||
});
|
||||
linkService.setHistory(this.pdfHistory);
|
||||
|
||||
document.getElementById("previous").addEventListener("click", function () {
|
||||
PDFViewerApplication.page--;
|
||||
});
|
||||
|
||||
document.getElementById("next").addEventListener("click", function () {
|
||||
PDFViewerApplication.page++;
|
||||
});
|
||||
|
||||
document.getElementById("zoomIn").addEventListener("click", function () {
|
||||
PDFViewerApplication.zoomIn();
|
||||
});
|
||||
|
||||
document.getElementById("zoomOut").addEventListener("click", function () {
|
||||
PDFViewerApplication.zoomOut();
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("pageNumber")
|
||||
.addEventListener("click", function () {
|
||||
this.select();
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("pageNumber")
|
||||
.addEventListener("change", function () {
|
||||
PDFViewerApplication.page = this.value | 0;
|
||||
|
||||
// Ensure that the page number input displays the correct value,
|
||||
// even if the value entered by the user was invalid
|
||||
// (e.g. a floating point number).
|
||||
if (this.value !== PDFViewerApplication.page.toString()) {
|
||||
this.value = PDFViewerApplication.page;
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.on("pagesinit", function () {
|
||||
// We can use pdfViewer now, e.g. let's change default scale.
|
||||
pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
|
||||
});
|
||||
|
||||
eventBus.on(
|
||||
"pagechanging",
|
||||
function (evt) {
|
||||
const page = evt.pageNumber;
|
||||
const numPages = PDFViewerApplication.pagesCount;
|
||||
|
||||
document.getElementById("pageNumber").value = page;
|
||||
document.getElementById("previous").disabled = page <= 1;
|
||||
document.getElementById("next").disabled = page >= numPages;
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
window.PDFViewerApplication = PDFViewerApplication;
|
||||
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function () {
|
||||
PDFViewerApplication.initUI();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// The offsetParent is not set until the PDF.js iframe or object is visible;
|
||||
// waiting for first animation.
|
||||
const animationStarted = new Promise(function (resolve) {
|
||||
window.requestAnimationFrame(resolve);
|
||||
});
|
||||
|
||||
// We need to delay opening until all HTML is loaded.
|
||||
animationStarted.then(function () {
|
||||
PDFViewerApplication.open({
|
||||
url: DEFAULT_URL,
|
||||
});
|
||||
});
|
||||
76
examples/node/getinfo.mjs
Normal file
@@ -0,0 +1,76 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
//
|
||||
// Basic node example that prints document metadata and text content.
|
||||
//
|
||||
|
||||
// Run `gulp dist-install` to generate 'pdfjs-dist' npm package files.
|
||||
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
||||
|
||||
// Loading file from file system into typed array
|
||||
const pdfPath =
|
||||
process.argv[2] || "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
|
||||
// Will be using promises to load document, pages and misc data instead of
|
||||
// callback.
|
||||
const loadingTask = getDocument({ url: pdfPath });
|
||||
loadingTask.promise
|
||||
.then(function (doc) {
|
||||
const numPages = doc.numPages;
|
||||
console.log("# Document Loaded");
|
||||
console.log("Number of Pages: " + numPages);
|
||||
console.log();
|
||||
|
||||
let lastPromise; // will be used to chain promises
|
||||
lastPromise = doc.getMetadata().then(function (data) {
|
||||
console.log("# Metadata Is Loaded");
|
||||
console.log("## Info");
|
||||
console.log(JSON.stringify(data.info, null, 2));
|
||||
console.log();
|
||||
if (data.metadata) {
|
||||
console.log("## Metadata");
|
||||
console.log(JSON.stringify(data.metadata.getAll(), null, 2));
|
||||
console.log();
|
||||
}
|
||||
});
|
||||
|
||||
const loadPage = function (pageNum) {
|
||||
return doc.getPage(pageNum).then(function (page) {
|
||||
console.log("# Page " + pageNum);
|
||||
const viewport = page.getViewport({ scale: 1.0 });
|
||||
console.log("Size: " + viewport.width + "x" + viewport.height);
|
||||
console.log();
|
||||
return page
|
||||
.getTextContent()
|
||||
.then(function (content) {
|
||||
// Content contains lots of information about the text layout and
|
||||
// styles, but we need only strings at the moment
|
||||
const strings = content.items.map(function (item) {
|
||||
return item.str;
|
||||
});
|
||||
console.log("## Text Content");
|
||||
console.log(strings.join(" "));
|
||||
// Release page resources.
|
||||
page.cleanup();
|
||||
})
|
||||
.then(function () {
|
||||
console.log();
|
||||
});
|
||||
});
|
||||
};
|
||||
// Loading of the first page will wait on metadata and subsequent loadings
|
||||
// will wait on the previous pages.
|
||||
for (let i = 1; i <= numPages; i++) {
|
||||
lastPromise = lastPromise.then(loadPage.bind(null, i));
|
||||
}
|
||||
return lastPromise;
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
console.log("# End of Document");
|
||||
},
|
||||
function (err) {
|
||||
console.error("Error: " + err);
|
||||
}
|
||||
);
|
||||
15
examples/node/pdf2png/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Overview
|
||||
|
||||
Example to demonstrate converting a PDF file to a PNG image using the PDF.js library.
|
||||
|
||||
## Getting started
|
||||
|
||||
Install the dependencies and build the PDF.js library:
|
||||
|
||||
$ npm install
|
||||
$ gulp dist-install
|
||||
|
||||
Run the example to convert the first page of a PDF file to a PNG image:
|
||||
|
||||
$ cd examples/node/pdf2png
|
||||
$ node pdf2png.mjs
|
||||
72
examples/node/pdf2png/pdf2png.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Copyright 2017 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
||||
|
||||
// Some PDFs need external cmaps.
|
||||
const CMAP_URL = "../../../node_modules/pdfjs-dist/cmaps/";
|
||||
const CMAP_PACKED = true;
|
||||
|
||||
// Where the standard fonts are located.
|
||||
const STANDARD_FONT_DATA_URL =
|
||||
"../../../node_modules/pdfjs-dist/standard_fonts/";
|
||||
|
||||
// Loading file from file system into typed array.
|
||||
const pdfPath =
|
||||
process.argv[2] || "../../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
const data = new Uint8Array(fs.readFileSync(pdfPath));
|
||||
|
||||
// Load the PDF file.
|
||||
const loadingTask = getDocument({
|
||||
data,
|
||||
cMapUrl: CMAP_URL,
|
||||
cMapPacked: CMAP_PACKED,
|
||||
standardFontDataUrl: STANDARD_FONT_DATA_URL,
|
||||
});
|
||||
|
||||
try {
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
console.log("# PDF document loaded.");
|
||||
// Get the first page.
|
||||
const page = await pdfDocument.getPage(1);
|
||||
// Render the page on a Node canvas with 100% scale.
|
||||
const canvasFactory = pdfDocument.canvasFactory;
|
||||
const viewport = page.getViewport({ scale: 1.0 });
|
||||
const canvasAndContext = canvasFactory.create(
|
||||
viewport.width,
|
||||
viewport.height
|
||||
);
|
||||
const renderContext = {
|
||||
canvasContext: canvasAndContext.context,
|
||||
viewport,
|
||||
};
|
||||
|
||||
const renderTask = page.render(renderContext);
|
||||
await renderTask.promise;
|
||||
// Convert the canvas to an image buffer.
|
||||
const image = canvasAndContext.canvas.toBuffer("image/png");
|
||||
fs.writeFile("output.png", image, function (error) {
|
||||
if (error) {
|
||||
console.error("Error: " + error);
|
||||
} else {
|
||||
console.log("Finished converting first page of PDF file to a PNG image.");
|
||||
}
|
||||
});
|
||||
// Release page resources.
|
||||
page.cleanup();
|
||||
} catch (reason) {
|
||||
console.log(reason);
|
||||
}
|
||||
13
examples/text-only/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Text-only PDF.js example</title>
|
||||
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
|
||||
<script src="pdf2svg.mjs" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Text-only PDF.js example</p>
|
||||
<div id="pageContainer" style="display: inline-block; border: solid 1px black"></div>
|
||||
</body>
|
||||
</html>
|
||||
72
examples/text-only/pdf2svg.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const PDF_PATH = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
const PAGE_NUMBER = 1;
|
||||
const PAGE_SCALE = 1.5;
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
|
||||
|
||||
function buildSVG(viewport, textContent) {
|
||||
// Building SVG with size of the viewport (for simplicity)
|
||||
const svg = document.createElementNS(SVG_NS, "svg:svg");
|
||||
svg.setAttribute("width", viewport.width + "px");
|
||||
svg.setAttribute("height", viewport.height + "px");
|
||||
// items are transformed to have 1px font size
|
||||
svg.setAttribute("font-size", 1);
|
||||
|
||||
// processing all items
|
||||
textContent.items.forEach(function (textItem) {
|
||||
// we have to take in account viewport transform, which includes scale,
|
||||
// rotation and Y-axis flip, and not forgetting to flip text.
|
||||
const tx = pdfjsLib.Util.transform(
|
||||
pdfjsLib.Util.transform(viewport.transform, textItem.transform),
|
||||
[1, 0, 0, -1, 0, 0]
|
||||
);
|
||||
const style = textContent.styles[textItem.fontName];
|
||||
// adding text element
|
||||
const text = document.createElementNS(SVG_NS, "svg:text");
|
||||
text.setAttribute("transform", "matrix(" + tx.join(" ") + ")");
|
||||
text.setAttribute("font-family", style.fontFamily);
|
||||
text.textContent = textItem.str;
|
||||
svg.append(text);
|
||||
});
|
||||
return svg;
|
||||
}
|
||||
|
||||
async function pageLoaded() {
|
||||
// Loading document and page text content
|
||||
const loadingTask = pdfjsLib.getDocument({ url: PDF_PATH });
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
const page = await pdfDocument.getPage(PAGE_NUMBER);
|
||||
const viewport = page.getViewport({ scale: PAGE_SCALE });
|
||||
const textContent = await page.getTextContent();
|
||||
// building SVG and adding that to the DOM
|
||||
const svg = buildSVG(viewport, textContent);
|
||||
document.getElementById("pageContainer").append(svg);
|
||||
// Release page resources.
|
||||
page.cleanup();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (typeof pdfjsLib === "undefined") {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
||||
return;
|
||||
}
|
||||
pageLoaded();
|
||||
});
|
||||
1
examples/webpack/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
33
examples/webpack/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## Overview
|
||||
|
||||
Example to demonstrate PDF.js library usage with Webpack.
|
||||
|
||||
## Getting started
|
||||
|
||||
Install the example dependencies and build the project:
|
||||
|
||||
$ gulp dist-install
|
||||
$ cd examples/webpack
|
||||
$ npm install
|
||||
$ ./node_modules/webpack/bin/webpack.js
|
||||
|
||||
You can observe the build results by running `gulp server` and navigating to
|
||||
http://localhost:8888/examples/webpack/index.html.
|
||||
|
||||
Refer to the `main.js` and `webpack.config.js` files for the source code.
|
||||
Note that PDF.js packaging requires packaging of the main application and
|
||||
the worker code, and the `workerSrc` path shall be set to the latter file.
|
||||
|
||||
### Minification
|
||||
|
||||
If you are configuring Webpack to output a minified build, please note that you
|
||||
*must* configure the minifier to keep original class/function names intact;
|
||||
otherwise the build is not guaranteed to work correctly.
|
||||
|
||||
## Worker loading
|
||||
|
||||
If you are getting the `Setting up fake worker` warning, make sure you are
|
||||
importing `pdfjs-dist/webpack.mjs` which is the zero-configuration method for
|
||||
Webpack users. Installing `worker-loader` is no longer necessary.
|
||||
|
||||
import * as pdfjsLib from 'pdfjs-dist/webpack.mjs';
|
||||
11
examples/webpack/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>webpack example</title>
|
||||
<script src="../../build/webpack/main.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="theCanvas"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
29
examples/webpack/main.mjs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
// Hello world example for webpack.
|
||||
|
||||
import * as pdfjsLib from "pdfjs-dist";
|
||||
|
||||
const pdfPath = "../learning/helloworld.pdf";
|
||||
|
||||
// Setting worker path to worker bundle.
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||||
"../../build/webpack/pdf.worker.bundle.js";
|
||||
|
||||
// Loading a document.
|
||||
const loadingTask = pdfjsLib.getDocument(pdfPath);
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Request a first page
|
||||
const pdfPage = await pdfDocument.getPage(1);
|
||||
// Display page on the existing canvas with 100% scale.
|
||||
const viewport = pdfPage.getViewport({ scale: 1.0 });
|
||||
const canvas = document.getElementById("theCanvas");
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: ctx,
|
||||
viewport,
|
||||
});
|
||||
await renderTask.promise;
|
||||
12
examples/webpack/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "webpack-pdf.js-example",
|
||||
"version": "0.2.0",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"pdfjs-dist": "../../node_modules/pdfjs-dist"
|
||||
}
|
||||
}
|
||||
18
examples/webpack/webpack.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
const webpack = require("webpack"); // eslint-disable-line no-unused-vars
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
context: __dirname,
|
||||
entry: {
|
||||
main: "./main.mjs",
|
||||
"pdf.worker": "pdfjs-dist/build/pdf.worker.mjs",
|
||||
},
|
||||
mode: "none",
|
||||
output: {
|
||||
path: path.join(__dirname, "../../build/webpack"),
|
||||
publicPath: "../../build/webpack/",
|
||||
filename: "[name].bundle.js",
|
||||
},
|
||||
};
|
||||
1
extensions/chromium/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
content/
|
||||
26
extensions/chromium/background.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2024 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
importScripts(
|
||||
"options/migration.js",
|
||||
"preserve-referer.js",
|
||||
"pdfHandler.js",
|
||||
"extension-router.js",
|
||||
"suppress-update.js",
|
||||
"telemetry.js"
|
||||
);
|
||||
261
extensions/chromium/contentscript.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2014 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
|
||||
|
||||
function getViewerURL(pdf_url) {
|
||||
return VIEWER_URL + "?file=" + encodeURIComponent(pdf_url);
|
||||
}
|
||||
|
||||
document.addEventListener("animationstart", onAnimationStart, true);
|
||||
if (document.contentType === "application/pdf") {
|
||||
chrome.runtime.sendMessage({ action: "canRequestBody" }, maybeRenderPdfDoc);
|
||||
}
|
||||
|
||||
function onAnimationStart(event) {
|
||||
if (event.animationName === "pdfjs-detected-object-or-embed") {
|
||||
watchObjectOrEmbed(event.target);
|
||||
}
|
||||
}
|
||||
|
||||
// Called for every <object> or <embed> element in the page.
|
||||
// This may change the type, src/data attributes and/or the child nodes of the
|
||||
// element. This function only affects elements for the first call. Subsequent
|
||||
// invocations have no effect.
|
||||
function watchObjectOrEmbed(elem) {
|
||||
var mimeType = elem.type;
|
||||
if (mimeType && mimeType.toLowerCase() !== "application/pdf") {
|
||||
return;
|
||||
}
|
||||
// <embed src> <object data>
|
||||
var srcAttribute = "src" in elem ? "src" : "data";
|
||||
var path = elem[srcAttribute];
|
||||
if (!mimeType && !/\.pdf(?:$|[?#])/i.test(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
elem.tagName === "EMBED" &&
|
||||
elem.name === "plugin" &&
|
||||
elem.parentNode === document.body &&
|
||||
elem.parentNode.childElementCount === 1 &&
|
||||
elem.src === location.href
|
||||
) {
|
||||
// This page is most likely Chrome's default page that embeds a PDF file.
|
||||
// The fact that the extension's background page did not intercept and
|
||||
// redirect this PDF request means that this PDF cannot be opened by PDF.js,
|
||||
// e.g. because it is a response to a POST request (as in #6174).
|
||||
// A reduced test case to test PDF response to POST requests is available at
|
||||
// https://robwu.nl/pdfjs/issue6174/.
|
||||
// Until #4483 is fixed, POST requests should be ignored.
|
||||
return;
|
||||
}
|
||||
if (elem.tagName === "EMBED" && elem.src === "about:blank") {
|
||||
// Starting from Chrome 76, internal embeds do not have the original URL,
|
||||
// but "about:blank" instead.
|
||||
// See https://github.com/mozilla/pdf.js/issues/11137
|
||||
return;
|
||||
}
|
||||
|
||||
if (elem.__I_saw_this_element) {
|
||||
return;
|
||||
}
|
||||
elem.__I_saw_this_element = true;
|
||||
|
||||
var tagName = elem.tagName.toUpperCase();
|
||||
var updateEmbedOrObject;
|
||||
if (tagName === "EMBED") {
|
||||
updateEmbedOrObject = updateEmbedElement;
|
||||
} else if (tagName === "OBJECT") {
|
||||
updateEmbedOrObject = updateObjectElement;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastSrc;
|
||||
var isUpdating = false;
|
||||
|
||||
function updateViewerFrame() {
|
||||
if (!isUpdating) {
|
||||
isUpdating = true;
|
||||
try {
|
||||
if (lastSrc !== elem[srcAttribute]) {
|
||||
updateEmbedOrObject(elem);
|
||||
lastSrc = elem[srcAttribute];
|
||||
}
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateViewerFrame();
|
||||
|
||||
// Watch for page-initiated changes of the src/data attribute.
|
||||
var srcObserver = new MutationObserver(updateViewerFrame);
|
||||
srcObserver.observe(elem, {
|
||||
attributes: true,
|
||||
childList: false,
|
||||
characterData: false,
|
||||
attributeFilter: [srcAttribute],
|
||||
});
|
||||
}
|
||||
|
||||
// Display the PDF Viewer in an <embed>.
|
||||
function updateEmbedElement(elem) {
|
||||
if (elem.type === "text/html" && elem.src.lastIndexOf(VIEWER_URL, 0) === 0) {
|
||||
// The viewer is already shown.
|
||||
return;
|
||||
}
|
||||
// The <embed> tag needs to be removed and re-inserted before any src changes
|
||||
// are effective.
|
||||
var parentNode = elem.parentNode;
|
||||
var nextSibling = elem.nextSibling;
|
||||
if (parentNode) {
|
||||
elem.remove();
|
||||
}
|
||||
elem.type = "text/html";
|
||||
elem.src = getEmbeddedViewerURL(elem.src);
|
||||
|
||||
if (parentNode) {
|
||||
// Suppress linter warning: insertBefore is preferable to
|
||||
// nextSibling.before(elem) because nextSibling may be null.
|
||||
// eslint-disable-next-line unicorn/prefer-modern-dom-apis
|
||||
parentNode.insertBefore(elem, nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the PDF Viewer in an <object>.
|
||||
function updateObjectElement(elem) {
|
||||
// <object> elements are terrible. Experiments (in49.0.2623.75) show that the
|
||||
// following happens:
|
||||
// - When fallback content is shown (e.g. because the built-in PDF Viewer is
|
||||
// disabled), updating the "data" attribute has no effect. Not surprising
|
||||
// considering that HTMLObjectElement::m_useFallbackContent is not reset
|
||||
// once it is set to true. Source:
|
||||
// WebKit/Source/core/html/HTMLObjectElement.cpp#378 (rev 749fe30d676b6c14).
|
||||
// - When the built-in PDF Viewer plugin is enabled, updating the "data"
|
||||
// attribute reloads the content (provided that the type was correctly set).
|
||||
// - When <object type=text/html data="chrome-extension://..."> is used
|
||||
// (tested with a data-URL, data:text/html,<object...>, the extension's
|
||||
// origin allowlist is not set up, so the viewer can't load the PDF file.
|
||||
// - The content of the <object> tag may be affected by <param> tags.
|
||||
//
|
||||
// To make sure that our solution works for all cases, we will insert a frame
|
||||
// as fallback content and force the <object> tag to render its fallback
|
||||
// content.
|
||||
var iframe = elem.firstElementChild;
|
||||
if (!iframe || !iframe.__inserted_by_pdfjs) {
|
||||
iframe = createFullSizeIframe();
|
||||
elem.textContent = "";
|
||||
elem.append(iframe);
|
||||
iframe.__inserted_by_pdfjs = true;
|
||||
}
|
||||
iframe.src = getEmbeddedViewerURL(elem.data);
|
||||
|
||||
// Some bogus content type that is not handled by any plugin.
|
||||
elem.type = "application/not-a-pee-dee-eff-type";
|
||||
// Force the <object> to reload and render its fallback content.
|
||||
elem.data += "";
|
||||
|
||||
// Usually the browser renders plugin content in this tag, which is completely
|
||||
// oblivious of styles such as padding, but we insert and render child nodes,
|
||||
// so force padding to be zero to avoid undesired dimension changes.
|
||||
elem.style.padding = "0";
|
||||
|
||||
// <object> and <embed> elements have a "display:inline" style by default.
|
||||
// Despite this property, when a plugin is loaded in the tag, the tag is
|
||||
// treated like "display:inline-block". However, when the browser does not
|
||||
// render plugin content, the <object> tag does not behave like that, and as
|
||||
// a result the width and height is ignored.
|
||||
// Force "display:inline-block" to make sure that the width/height as set by
|
||||
// web pages is respected.
|
||||
// (<embed> behaves as expected with the default display value, but setting it
|
||||
// to display:inline-block doesn't hurt).
|
||||
elem.style.display = "inline-block";
|
||||
}
|
||||
|
||||
// Create an <iframe> element without borders that takes the full width and
|
||||
// height.
|
||||
function createFullSizeIframe() {
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.style.background = "none";
|
||||
iframe.style.border = "none";
|
||||
iframe.style.borderRadius = "none";
|
||||
iframe.style.boxShadow = "none";
|
||||
iframe.style.cssFloat = "none";
|
||||
iframe.style.display = "block";
|
||||
iframe.style.height = "100%";
|
||||
iframe.style.margin = "0";
|
||||
iframe.style.maxHeight = "none";
|
||||
iframe.style.maxWidth = "none";
|
||||
iframe.style.position = "static";
|
||||
iframe.style.transform = "none";
|
||||
iframe.style.visibility = "visible";
|
||||
iframe.style.width = "100%";
|
||||
return iframe;
|
||||
}
|
||||
|
||||
// Get the viewer URL, provided that the path is a valid URL.
|
||||
function getEmbeddedViewerURL(path) {
|
||||
var fragment = /^([^#]*)(#.*)?$/.exec(path);
|
||||
path = fragment[1];
|
||||
fragment = fragment[2] || "";
|
||||
|
||||
// Resolve relative path to document.
|
||||
var a = document.createElement("a");
|
||||
a.href = document.baseURI;
|
||||
a.href = path;
|
||||
path = a.href;
|
||||
return getViewerURL(path) + fragment;
|
||||
}
|
||||
|
||||
function maybeRenderPdfDoc(isNotPOST) {
|
||||
if (!isNotPOST) {
|
||||
// The document was loaded through a POST request, but we cannot access the
|
||||
// original response body, nor safely send a new request to fetch the PDF.
|
||||
// Until #4483 is fixed, POST requests should be ignored.
|
||||
return;
|
||||
}
|
||||
|
||||
// Detected PDF that was not redirected by the declarativeNetRequest rules.
|
||||
// Maybe because this was served without Content-Type and sniffed as PDF.
|
||||
// Or because this is Chrome 127-, which does not support responseHeaders
|
||||
// condition in declarativeNetRequest (DNR), and PDF requests are therefore
|
||||
// not redirected via DNR.
|
||||
|
||||
// In any case, load the viewer.
|
||||
console.log(`Detected PDF via document, opening viewer for ${document.URL}`);
|
||||
|
||||
// Ideally we would use logic consistent with the DNR logic, like this:
|
||||
// location.href = getEmbeddedViewerURL(document.URL);
|
||||
// ... unfortunately, this causes Chrome to crash until version 129, fixed by
|
||||
// https://chromium.googlesource.com/chromium/src/+/8c42358b2cc549553d939efe7d36515d80563da7%5E%21/
|
||||
// Work around this by replacing the body with an iframe of the viewer.
|
||||
// Interestingly, Chrome's built-in PDF viewer uses a similar technique.
|
||||
const shadowRoot = document.body.attachShadow({ mode: "closed" });
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.top = "0";
|
||||
iframe.style.left = "0";
|
||||
iframe.style.width = "100%";
|
||||
iframe.style.height = "100%";
|
||||
iframe.style.border = "0 none";
|
||||
iframe.src = getEmbeddedViewerURL(document.URL);
|
||||
shadowRoot.append(iframe);
|
||||
}
|
||||
14
extensions/chromium/contentstyle.css
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Detect creation of <embed> and <object> tags.
|
||||
*/
|
||||
@keyframes pdfjs-detected-object-or-embed {
|
||||
from {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
object,
|
||||
embed {
|
||||
animation-delay: 0s !important;
|
||||
animation-name: pdfjs-detected-object-or-embed !important;
|
||||
animation-play-state: running !important;
|
||||
}
|
||||
105
extensions/chromium/extension-router.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright 2013 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
(function ExtensionRouterClosure() {
|
||||
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
|
||||
var CRX_BASE_URL = chrome.runtime.getURL("/");
|
||||
|
||||
var schemes = [
|
||||
"http",
|
||||
"https",
|
||||
"file",
|
||||
"chrome-extension",
|
||||
"blob",
|
||||
"data",
|
||||
// Chromium OS
|
||||
"filesystem",
|
||||
// Chromium OS, shorthand for filesystem:<origin>/external/
|
||||
"drive",
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string} url The URL prefixed with chrome-extension://.../
|
||||
* @returns {string|undefined} The percent-encoded URL of the (PDF) file.
|
||||
*/
|
||||
function parseExtensionURL(url) {
|
||||
url = url.substring(CRX_BASE_URL.length);
|
||||
// Find the (url-encoded) colon and verify that the scheme is allowed.
|
||||
var schemeIndex = url.search(/:|%3A/i);
|
||||
if (schemeIndex === -1) {
|
||||
return undefined;
|
||||
}
|
||||
var scheme = url.slice(0, schemeIndex).toLowerCase();
|
||||
if (schemes.includes(scheme)) {
|
||||
// NOTE: We cannot use the `updateUrlHash` function in this context.
|
||||
url = url.split("#", 1)[0];
|
||||
if (url.charAt(schemeIndex) === ":") {
|
||||
url = encodeURIComponent(url);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveViewerURL(originalUrl) {
|
||||
if (originalUrl.startsWith(CRX_BASE_URL)) {
|
||||
// This listener converts chrome-extension://.../http://...pdf to
|
||||
// chrome-extension://.../content/web/viewer.html?file=http%3A%2F%2F...pdf
|
||||
var url = parseExtensionURL(originalUrl);
|
||||
if (url) {
|
||||
url = VIEWER_URL + "?file=" + url;
|
||||
var i = originalUrl.indexOf("#");
|
||||
if (i > 0) {
|
||||
url += originalUrl.slice(i);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
self.addEventListener("fetch", event => {
|
||||
const req = event.request;
|
||||
if (req.destination === "document") {
|
||||
var url = resolveViewerURL(req.url);
|
||||
if (url) {
|
||||
console.log("Redirecting " + req.url + " to " + url);
|
||||
event.respondWith(Response.redirect(url));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ctrl + F5 bypasses service worker. the pretty extension URLs will fail to
|
||||
// resolve in that case. Catch this and redirect to destination.
|
||||
chrome.webNavigation.onErrorOccurred.addListener(
|
||||
details => {
|
||||
if (details.frameId !== 0) {
|
||||
// Not a top-level frame. Cannot easily navigate a specific child frame.
|
||||
return;
|
||||
}
|
||||
const url = resolveViewerURL(details.url);
|
||||
if (url) {
|
||||
console.log(`Redirecting ${details.url} to ${url} (fallback)`);
|
||||
chrome.tabs.update(details.tabId, { url });
|
||||
}
|
||||
},
|
||||
{ url: [{ urlPrefix: CRX_BASE_URL }] }
|
||||
);
|
||||
|
||||
console.log("Set up extension URL router.");
|
||||
})();
|
||||
BIN
extensions/chromium/icon128.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
extensions/chromium/icon16.png
Normal file
|
After Width: | Height: | Size: 594 B |
BIN
extensions/chromium/icon48.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
61
extensions/chromium/manifest.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"minimum_chrome_version": "103",
|
||||
"manifest_version": 3,
|
||||
"name": "PDF Viewer",
|
||||
"version": "PDFJSSCRIPT_VERSION",
|
||||
"description": "Uses HTML5 to display PDF files directly in the browser.",
|
||||
"icons": {
|
||||
"128": "icon128.png",
|
||||
"48": "icon48.png",
|
||||
"16": "icon16.png"
|
||||
},
|
||||
"permissions": [
|
||||
"alarms",
|
||||
"declarativeNetRequestWithHostAccess",
|
||||
"webRequest",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"storage"
|
||||
],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["http://*/*", "https://*/*", "file://*/*"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true,
|
||||
"css": ["contentstyle.css"],
|
||||
"js": ["contentscript.js"]
|
||||
}
|
||||
],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"storage": {
|
||||
"managed_schema": "preferences_schema.json"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options/options.html"
|
||||
},
|
||||
"options_page": "options/options.html",
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"incognito": "split",
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"content/web/viewer.html",
|
||||
"http:/*",
|
||||
"https:/*",
|
||||
"file:/*",
|
||||
"chrome-extension:/*",
|
||||
"blob:*",
|
||||
"data:*",
|
||||
"filesystem:/*",
|
||||
"drive:*"
|
||||
],
|
||||
"matches": ["<all_urls>"],
|
||||
"extension_ids": ["*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
153
extensions/chromium/options/migration.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
Copyright 2016 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
chrome.runtime.onInstalled.addListener(({ reason }) => {
|
||||
if (reason !== "update") {
|
||||
// We only need to run migration logic for extension updates, not for new
|
||||
// installs or browser updates.
|
||||
return;
|
||||
}
|
||||
var storageLocal = chrome.storage.local;
|
||||
var storageSync = chrome.storage.sync;
|
||||
|
||||
if (!storageSync) {
|
||||
// No sync storage area - nothing to migrate to.
|
||||
return;
|
||||
}
|
||||
|
||||
getStorageNames(function (storageKeys) {
|
||||
storageLocal.get(storageKeys, function (values) {
|
||||
if (!values || !Object.keys(values).length) {
|
||||
// No local storage - nothing to migrate.
|
||||
// ... except possibly for a renamed preference name.
|
||||
migrateRenamedStorage();
|
||||
return;
|
||||
}
|
||||
migrateToSyncStorage(values);
|
||||
});
|
||||
});
|
||||
|
||||
async function getStorageNames(callback) {
|
||||
var schema_location = chrome.runtime.getManifest().storage.managed_schema;
|
||||
var res = await fetch(chrome.runtime.getURL(schema_location));
|
||||
var storageManifest = await res.json();
|
||||
var storageKeys = Object.keys(storageManifest.properties);
|
||||
callback(storageKeys);
|
||||
}
|
||||
|
||||
// Save |values| to storage.sync and delete the values with that key from
|
||||
// storage.local.
|
||||
function migrateToSyncStorage(values) {
|
||||
storageSync.set(values, function () {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Failed to migrate settings due to an error: " +
|
||||
chrome.runtime.lastError.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Migration successful. Delete local settings.
|
||||
storageLocal.remove(Object.keys(values), function () {
|
||||
// In theory remove() could fail (e.g. if the browser's storage
|
||||
// backend is corrupt), but since storageSync.set succeeded, consider
|
||||
// the migration successful.
|
||||
console.log(
|
||||
"Successfully migrated preferences from local to sync storage."
|
||||
);
|
||||
migrateRenamedStorage();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Remove this migration code somewhere in the future, when most users
|
||||
// have had their chance of migrating to the new preference format.
|
||||
// Note: We cannot modify managed preferences, so the migration logic is
|
||||
// duplicated in web/chromecom.js too.
|
||||
function migrateRenamedStorage() {
|
||||
storageSync.get(
|
||||
[
|
||||
"enableHandToolOnLoad",
|
||||
"cursorToolOnLoad",
|
||||
"disableTextLayer",
|
||||
"enhanceTextSelection",
|
||||
"textLayerMode",
|
||||
"showPreviousViewOnLoad",
|
||||
"disablePageMode",
|
||||
"viewOnLoad",
|
||||
],
|
||||
function (items) {
|
||||
// Migration code for https://github.com/mozilla/pdf.js/pull/7635.
|
||||
if (typeof items.enableHandToolOnLoad === "boolean") {
|
||||
if (items.enableHandToolOnLoad) {
|
||||
storageSync.set(
|
||||
{
|
||||
cursorToolOnLoad: 1,
|
||||
},
|
||||
function () {
|
||||
if (!chrome.runtime.lastError) {
|
||||
storageSync.remove("enableHandToolOnLoad");
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
storageSync.remove("enableHandToolOnLoad");
|
||||
}
|
||||
}
|
||||
// Migration code for https://github.com/mozilla/pdf.js/pull/9479.
|
||||
if (typeof items.disableTextLayer === "boolean") {
|
||||
if (items.disableTextLayer) {
|
||||
storageSync.set(
|
||||
{
|
||||
textLayerMode: 0,
|
||||
},
|
||||
function () {
|
||||
if (!chrome.runtime.lastError) {
|
||||
storageSync.remove([
|
||||
"disableTextLayer",
|
||||
"enhanceTextSelection",
|
||||
]);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
storageSync.remove(["disableTextLayer", "enhanceTextSelection"]);
|
||||
}
|
||||
}
|
||||
// Migration code for https://github.com/mozilla/pdf.js/pull/10502.
|
||||
if (typeof items.showPreviousViewOnLoad === "boolean") {
|
||||
if (!items.showPreviousViewOnLoad) {
|
||||
storageSync.set(
|
||||
{
|
||||
viewOnLoad: 1,
|
||||
},
|
||||
function () {
|
||||
if (!chrome.runtime.lastError) {
|
||||
storageSync.remove([
|
||||
"showPreviousViewOnLoad",
|
||||
"disablePageMode",
|
||||
]);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
storageSync.remove(["showPreviousViewOnLoad", "disablePageMode"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
185
extensions/chromium/options/options.html
Normal file
@@ -0,0 +1,185 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2015 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>PDF.js viewer options</title>
|
||||
<style>
|
||||
body {
|
||||
min-width: 400px; /* a page at the settings page is at least 400px wide */
|
||||
margin: 14px 17px; /* already added by default in Chrome 40.0.2212.0 */
|
||||
}
|
||||
.settings-row {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.checkbox label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.checkbox label input {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="settings-boxes"></div>
|
||||
<button id="reset-button" type="button">Restore default settings</button>
|
||||
|
||||
<template id="checkbox-template">
|
||||
<div class="settings-row checkbox">
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="viewerCssTheme-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="0">Use system theme</option>
|
||||
<option value="1">Light theme</option>
|
||||
<option value="2">Dark theme</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="viewOnLoad-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="-1">Default</option>
|
||||
<option value="0">Show previous position</option>
|
||||
<option value="1">Show initial position</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="defaultZoomValue-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="auto" selected="selected">Automatic Zoom</option>
|
||||
<option value="page-actual">Actual Size</option>
|
||||
<option value="page-fit">Page Fit</option>
|
||||
<option value="page-width">Page Width</option>
|
||||
<option value="custom" class="custom-zoom" hidden></option>
|
||||
<option value="50">50%</option>
|
||||
<option value="75">75%</option>
|
||||
<option value="100">100%</option>
|
||||
<option value="125">125%</option>
|
||||
<option value="150">150%</option>
|
||||
<option value="200">200%</option>
|
||||
<option value="300">300%</option>
|
||||
<option value="400">400%</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="sidebarViewOnLoad-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="-1">Default</option>
|
||||
<option value="0">Do not show sidebar</option>
|
||||
<option value="1">Show thumbnails in sidebar</option>
|
||||
<option value="2">Show document outline in sidebar</option>
|
||||
<option value="3">Show attachments in sidebar</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="cursorToolOnLoad-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="0">Text selection tool</option>
|
||||
<option value="1">Hand tool</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="textLayerMode-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="0">Disable text selection</option>
|
||||
<option value="1">Enable text selection</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="externalLinkTarget-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="0">Default</option>
|
||||
<option value="1">Current window/tab</option>
|
||||
<option value="2">New window/tab</option>
|
||||
<option value="3">Parent window/tab</option>
|
||||
<option value="4">Top window/tab</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="scrollModeOnLoad-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="-1">Default</option>
|
||||
<option value="3">Page scrolling</option>
|
||||
<option value="0">Vertical scrolling</option>
|
||||
<option value="1">Horizontal scrolling</option>
|
||||
<option value="2">Wrapped scrolling</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="spreadModeOnLoad-template">
|
||||
<div class="settings-row">
|
||||
<label>
|
||||
<span></span>
|
||||
<select>
|
||||
<option value="-1">Default</option>
|
||||
<option value="0">No spreads</option>
|
||||
<option value="1">Odd spreads</option>
|
||||
<option value="2">Even spreads</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
210
extensions/chromium/options/options.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Copyright 2015 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var storageAreaName = chrome.storage.sync ? "sync" : "local";
|
||||
var storageArea = chrome.storage[storageAreaName];
|
||||
|
||||
Promise.all([
|
||||
new Promise(function getManagedPrefs(resolve) {
|
||||
if (!chrome.storage.managed) {
|
||||
resolve({});
|
||||
return;
|
||||
}
|
||||
// Get preferences as set by the system administrator.
|
||||
chrome.storage.managed.get(null, function (prefs) {
|
||||
// Managed storage may be disabled, e.g. in Opera.
|
||||
resolve(prefs || {});
|
||||
});
|
||||
}),
|
||||
new Promise(function getUserPrefs(resolve) {
|
||||
storageArea.get(null, function (prefs) {
|
||||
resolve(prefs || {});
|
||||
});
|
||||
}),
|
||||
new Promise(function getStorageSchema(resolve) {
|
||||
// Get the storage schema - a dictionary of preferences.
|
||||
var x = new XMLHttpRequest();
|
||||
var schema_location = chrome.runtime.getManifest().storage.managed_schema;
|
||||
x.open("get", chrome.runtime.getURL(schema_location));
|
||||
x.onload = function () {
|
||||
resolve(x.response.properties);
|
||||
};
|
||||
x.responseType = "json";
|
||||
x.send();
|
||||
}),
|
||||
])
|
||||
.then(function (values) {
|
||||
var managedPrefs = values[0];
|
||||
var userPrefs = values[1];
|
||||
var schema = values[2];
|
||||
function getPrefValue(prefName) {
|
||||
if (prefName in userPrefs) {
|
||||
return userPrefs[prefName];
|
||||
} else if (prefName in managedPrefs) {
|
||||
return managedPrefs[prefName];
|
||||
}
|
||||
return schema[prefName].default;
|
||||
}
|
||||
var prefNames = Object.keys(schema);
|
||||
var renderPreferenceFunctions = {};
|
||||
// Render options
|
||||
prefNames.forEach(function (prefName) {
|
||||
var prefSchema = schema[prefName];
|
||||
if (!prefSchema.title) {
|
||||
// Don't show preferences if the title is missing.
|
||||
return;
|
||||
}
|
||||
|
||||
// A DOM element with a method renderPreference.
|
||||
var renderPreference;
|
||||
if (prefSchema.type === "boolean") {
|
||||
// Most prefs are booleans, render them in a generic way.
|
||||
renderPreference = renderBooleanPref(
|
||||
prefSchema.title,
|
||||
prefSchema.description,
|
||||
prefName
|
||||
);
|
||||
} else if (prefSchema.type === "integer" && prefSchema.enum) {
|
||||
// Most other prefs are integer-valued enumerations, render them in a
|
||||
// generic way too.
|
||||
// Unlike the renderBooleanPref branch, each preference handled by this
|
||||
// branch still needs its own template in options.html with
|
||||
// id="$prefName-template".
|
||||
renderPreference = renderEnumPref(prefSchema.title, prefName);
|
||||
} else if (prefName === "defaultZoomValue") {
|
||||
renderPreference = renderDefaultZoomValue(prefSchema.title);
|
||||
} else {
|
||||
// Should NEVER be reached. Only happens if a new type of preference is
|
||||
// added to the storage manifest.
|
||||
console.error("Don't know how to handle " + prefName + "!");
|
||||
return;
|
||||
}
|
||||
|
||||
renderPreference(getPrefValue(prefName));
|
||||
renderPreferenceFunctions[prefName] = renderPreference;
|
||||
});
|
||||
|
||||
// Names of preferences that are displayed in the UI.
|
||||
var renderedPrefNames = Object.keys(renderPreferenceFunctions);
|
||||
|
||||
// Reset button to restore default settings.
|
||||
document.getElementById("reset-button").onclick = function () {
|
||||
userPrefs = {};
|
||||
storageArea.remove(prefNames, function () {
|
||||
renderedPrefNames.forEach(function (prefName) {
|
||||
renderPreferenceFunctions[prefName](getPrefValue(prefName));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Automatically update the UI when the preferences were changed elsewhere.
|
||||
chrome.storage.onChanged.addListener(function (changes, areaName) {
|
||||
var prefs = null;
|
||||
if (areaName === storageAreaName) {
|
||||
prefs = userPrefs;
|
||||
} else if (areaName === "managed") {
|
||||
prefs = managedPrefs;
|
||||
}
|
||||
if (prefs) {
|
||||
renderedPrefNames.forEach(function (prefName) {
|
||||
var prefChanges = changes[prefName];
|
||||
if (prefChanges) {
|
||||
if ("newValue" in prefChanges) {
|
||||
userPrefs[prefName] = prefChanges.newValue;
|
||||
} else {
|
||||
// Otherwise the pref was deleted
|
||||
delete userPrefs[prefName];
|
||||
}
|
||||
renderPreferenceFunctions[prefName](getPrefValue(prefName));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(null, console.error.bind(console));
|
||||
|
||||
function importTemplate(id) {
|
||||
return document.importNode(document.getElementById(id).content, true);
|
||||
}
|
||||
|
||||
// Helpers to create UI elements that display the preference, and return a
|
||||
// function which updates the UI with the preference.
|
||||
|
||||
function renderBooleanPref(shortDescription, description, prefName) {
|
||||
var wrapper = importTemplate("checkbox-template");
|
||||
wrapper.title = description;
|
||||
|
||||
var checkbox = wrapper.querySelector('input[type="checkbox"]');
|
||||
checkbox.onchange = function () {
|
||||
var pref = {};
|
||||
pref[prefName] = this.checked;
|
||||
storageArea.set(pref);
|
||||
};
|
||||
wrapper.querySelector("span").textContent = shortDescription;
|
||||
document.getElementById("settings-boxes").append(wrapper);
|
||||
|
||||
function renderPreference(value) {
|
||||
checkbox.checked = value;
|
||||
}
|
||||
return renderPreference;
|
||||
}
|
||||
|
||||
function renderEnumPref(shortDescription, prefName) {
|
||||
var wrapper = importTemplate(prefName + "-template");
|
||||
var select = wrapper.querySelector("select");
|
||||
select.onchange = function () {
|
||||
var pref = {};
|
||||
pref[prefName] = parseInt(this.value, 10);
|
||||
storageArea.set(pref);
|
||||
};
|
||||
wrapper.querySelector("span").textContent = shortDescription;
|
||||
document.getElementById("settings-boxes").append(wrapper);
|
||||
|
||||
function renderPreference(value) {
|
||||
select.value = value;
|
||||
}
|
||||
return renderPreference;
|
||||
}
|
||||
|
||||
function renderDefaultZoomValue(shortDescription) {
|
||||
var wrapper = importTemplate("defaultZoomValue-template");
|
||||
var select = wrapper.querySelector("select");
|
||||
select.onchange = function () {
|
||||
storageArea.set({
|
||||
defaultZoomValue: this.value,
|
||||
});
|
||||
};
|
||||
wrapper.querySelector("span").textContent = shortDescription;
|
||||
document.getElementById("settings-boxes").append(wrapper);
|
||||
|
||||
function renderPreference(value) {
|
||||
value = value || "auto";
|
||||
select.value = value;
|
||||
var customOption = select.querySelector("option.custom-zoom");
|
||||
if (select.selectedIndex === -1 && value) {
|
||||
// Custom zoom percentage, e.g. set via managed preferences.
|
||||
// [zoom] or [zoom],[left],[top]
|
||||
customOption.text = value.indexOf(",") > 0 ? value : value + "%";
|
||||
customOption.value = value;
|
||||
customOption.hidden = false;
|
||||
customOption.selected = true;
|
||||
} else {
|
||||
customOption.hidden = true;
|
||||
}
|
||||
}
|
||||
return renderPreference;
|
||||
}
|
||||
368
extensions/chromium/pdfHandler.js
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
Copyright 2012 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* globals canRequestBody */ // From preserve-referer.js
|
||||
|
||||
"use strict";
|
||||
|
||||
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
|
||||
|
||||
// Use in-memory storage to ensure that the DNR rules have been registered at
|
||||
// least once per session. runtime.onInstalled would have been the most fitting
|
||||
// event to ensure that, except there are cases where it does not fire when
|
||||
// needed. E.g. in incognito mode: https://issues.chromium.org/issues/41029550
|
||||
chrome.storage.session.get({ hasPdfRedirector: false }, async items => {
|
||||
if (items?.hasPdfRedirector) {
|
||||
return;
|
||||
}
|
||||
const rules = await chrome.declarativeNetRequest.getDynamicRules();
|
||||
if (rules.length) {
|
||||
// Dynamic rules persist across extension updates. We don't expect other
|
||||
// dynamic rules, so just remove them all.
|
||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
||||
removeRuleIds: rules.map(r => r.id),
|
||||
});
|
||||
}
|
||||
await registerPdfRedirectRule();
|
||||
|
||||
// Only set the flag in the end, so that we know for sure that all
|
||||
// asynchronous initialization logic has run. If not, then we will run the
|
||||
// logic again at the next background wakeup.
|
||||
chrome.storage.session.set({ hasPdfRedirector: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers declarativeNetRequest rules to redirect PDF requests to the viewer.
|
||||
* The caller should clear any previously existing dynamic DNR rules.
|
||||
*
|
||||
* The logic here is the declarative version of the runtime logic in the
|
||||
* webRequest.onHeadersReceived implementation at
|
||||
* https://github.com/mozilla/pdf.js/blob/0676ea19cf17023ec8c2d6ad69a859c345c01dc1/extensions/chromium/pdfHandler.js#L34-L152
|
||||
*/
|
||||
async function registerPdfRedirectRule() {
|
||||
// "allow" means to ignore rules (from this extension) with lower priority.
|
||||
const ACTION_IGNORE_OTHER_RULES = { type: "allow" };
|
||||
|
||||
// Redirect to viewer. The rule condition is expected to specify regexFilter
|
||||
// that matches the full request URL.
|
||||
const ACTION_REDIRECT_TO_VIEWER = {
|
||||
type: "redirect",
|
||||
redirect: {
|
||||
// DNR does not support transformations such as encodeURIComponent on the
|
||||
// match, so we just concatenate the URL as is without modifications.
|
||||
// TODO: use "?file=\\0" when DNR supports transformations as proposed at
|
||||
// https://github.com/w3c/webextensions/issues/636#issuecomment-2165978322
|
||||
regexSubstitution: VIEWER_URL + "?DNR:\\0",
|
||||
},
|
||||
};
|
||||
|
||||
// Rules in order of priority (highest priority rule first).
|
||||
// The required "id" fields will be auto-generated later.
|
||||
const addRules = [
|
||||
{
|
||||
// Do not redirect for URLs containing pdfjs.action=download.
|
||||
condition: {
|
||||
urlFilter: "pdfjs.action=download",
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
},
|
||||
action: ACTION_IGNORE_OTHER_RULES,
|
||||
},
|
||||
{
|
||||
// Redirect local PDF files if isAllowedFileSchemeAccess is true. No-op
|
||||
// otherwise and then handled by webNavigation.onBeforeNavigate below.
|
||||
condition: {
|
||||
regexFilter: "^file://.*\\.pdf$",
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
},
|
||||
action: ACTION_REDIRECT_TO_VIEWER,
|
||||
},
|
||||
{
|
||||
// Respect the Content-Disposition:attachment header in sub_frame. But:
|
||||
// Display the PDF viewer regardless of the Content-Disposition header if
|
||||
// the file is displayed in the main frame, since most often users want to
|
||||
// view a PDF, and servers are often misconfigured.
|
||||
condition: {
|
||||
urlFilter: "*",
|
||||
resourceTypes: ["sub_frame"], // Note: no main_frame, handled below.
|
||||
responseHeaders: [
|
||||
{
|
||||
header: "content-disposition",
|
||||
values: ["attachment*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
action: ACTION_IGNORE_OTHER_RULES,
|
||||
},
|
||||
{
|
||||
// If the query string contains "=download", do not unconditionally force
|
||||
// viewer to open the PDF, but first check whether the Content-Disposition
|
||||
// header specifies an attachment. This allows sites like Google Drive to
|
||||
// operate correctly (#6106).
|
||||
condition: {
|
||||
urlFilter: "=download",
|
||||
resourceTypes: ["main_frame"], // No sub_frame, was handled before.
|
||||
responseHeaders: [
|
||||
{
|
||||
header: "content-disposition",
|
||||
values: ["attachment*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
action: ACTION_IGNORE_OTHER_RULES,
|
||||
},
|
||||
{
|
||||
// Regular http(s) PDF requests.
|
||||
condition: {
|
||||
regexFilter: "^.*$",
|
||||
// The viewer does not have the original request context and issues a
|
||||
// GET request. The original response to POST requests is unavailable.
|
||||
excludedRequestMethods: ["post"],
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
responseHeaders: [
|
||||
{
|
||||
header: "content-type",
|
||||
values: ["application/pdf", "application/pdf;*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
action: ACTION_REDIRECT_TO_VIEWER,
|
||||
},
|
||||
{
|
||||
// Wrong MIME-type, but a PDF file according to the file name in the URL.
|
||||
condition: {
|
||||
regexFilter: "^.*\\.pdf\\b.*$",
|
||||
// The viewer does not have the original request context and issues a
|
||||
// GET request. The original response to POST requests is unavailable.
|
||||
excludedRequestMethods: ["post"],
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
responseHeaders: [
|
||||
{
|
||||
header: "content-type",
|
||||
values: ["application/octet-stream", "application/octet-stream;*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
action: ACTION_REDIRECT_TO_VIEWER,
|
||||
},
|
||||
{
|
||||
// Wrong MIME-type, but a PDF file according to Content-Disposition.
|
||||
condition: {
|
||||
regexFilter: "^.*$",
|
||||
// The viewer does not have the original request context and issues a
|
||||
// GET request. The original response to POST requests is unavailable.
|
||||
excludedRequestMethods: ["post"],
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
responseHeaders: [
|
||||
{
|
||||
header: "content-disposition",
|
||||
values: ["*.pdf", '*.pdf"*', "*.pdf'*"],
|
||||
},
|
||||
],
|
||||
// We only want to match by content-disposition if Content-Type is set
|
||||
// to application/octet-stream. The responseHeaders condition is a
|
||||
// logical OR instead of AND, so to simulate the AND condition we use
|
||||
// the double negation of excludedResponseHeaders + excludedValues.
|
||||
// This matches any request whose content-type header is set and not
|
||||
// "application/octet-stream". It will also match if "content-type" is
|
||||
// not set, but we are okay with that since the browser would usually
|
||||
// try to sniff the MIME type in that case.
|
||||
excludedResponseHeaders: [
|
||||
{
|
||||
header: "content-type",
|
||||
excludedValues: [
|
||||
"application/octet-stream",
|
||||
"application/octet-stream;*",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
action: ACTION_REDIRECT_TO_VIEWER,
|
||||
},
|
||||
];
|
||||
for (const [i, rule] of addRules.entries()) {
|
||||
// id must be unique and at least 1, but i starts at 0. So add +1.
|
||||
rule.id = i + 1;
|
||||
rule.priority = addRules.length - i;
|
||||
}
|
||||
try {
|
||||
// Note: condition.responseHeaders is only supported in Chrome 128+, but
|
||||
// does not trigger errors in Chrome 123 - 127 as explained at:
|
||||
// https://github.com/w3c/webextensions/issues/638#issuecomment-2181124486
|
||||
// We need to detect this and avoid registering rules, because otherwise all
|
||||
// requests are redirected to the viewer instead of just PDF requests,
|
||||
// because Chrome accepts rules while ignoring the responseHeaders condition
|
||||
// - also reported at https://crbug.com/347186592
|
||||
if (!(await isHeaderConditionSupported())) {
|
||||
throw new Error("DNR responseHeaders condition is not supported.");
|
||||
}
|
||||
await chrome.declarativeNetRequest.updateDynamicRules({ addRules });
|
||||
} catch (e) {
|
||||
// When we do not register DNR rules for any reason, fall back to catching
|
||||
// PDF documents via maybeRenderPdfDoc in contentscript.js.
|
||||
console.error("Failed to register rules to redirect PDF requests.");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// For the source and explanation of this logic, see
|
||||
// https://github.com/w3c/webextensions/issues/638#issuecomment-2181124486
|
||||
async function isHeaderConditionSupported() {
|
||||
const ruleId = 123456; // Some rule ID that is not already used elsewhere.
|
||||
try {
|
||||
// Throws synchronously if not supported.
|
||||
await chrome.declarativeNetRequest.updateSessionRules({
|
||||
addRules: [
|
||||
{
|
||||
id: ruleId,
|
||||
condition: {
|
||||
responseHeaders: [{ header: "whatever" }],
|
||||
urlFilter: "|does_not_match_anything",
|
||||
},
|
||||
action: { type: "block" },
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch {
|
||||
return false; // responseHeaders condition not supported.
|
||||
}
|
||||
// Chrome may recognize the properties but have the implementation behind a
|
||||
// flag. When the implementation is disabled, validation is skipped too.
|
||||
try {
|
||||
await chrome.declarativeNetRequest.updateSessionRules({
|
||||
removeRuleIds: [ruleId],
|
||||
addRules: [
|
||||
{
|
||||
id: ruleId,
|
||||
condition: {
|
||||
responseHeaders: [],
|
||||
urlFilter: "|does_not_match_anything",
|
||||
},
|
||||
action: { type: "block" },
|
||||
},
|
||||
],
|
||||
});
|
||||
return false; // Validation skipped = feature disabled.
|
||||
} catch {
|
||||
return true; // Validation worked = feature enabled.
|
||||
} finally {
|
||||
await chrome.declarativeNetRequest.updateSessionRules({
|
||||
removeRuleIds: [ruleId],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getViewerURL(pdf_url) {
|
||||
// |pdf_url| may contain a fragment such as "#page=2". That should be passed
|
||||
// as a fragment to the viewer, not encoded in pdf_url.
|
||||
var hash = "";
|
||||
var i = pdf_url.indexOf("#");
|
||||
if (i > 0) {
|
||||
hash = pdf_url.slice(i);
|
||||
pdf_url = pdf_url.slice(0, i);
|
||||
}
|
||||
return VIEWER_URL + "?file=" + encodeURIComponent(pdf_url) + hash;
|
||||
}
|
||||
|
||||
// If the user has not granted access to file:-URLs, then declarativeNetRequest
|
||||
// will not catch the request. It is still visible through the webNavigation
|
||||
// API though, and we can replace the tab with the viewer.
|
||||
// The viewer will detect that it has no access to file:-URLs, and prompt the
|
||||
// user to activate file permissions.
|
||||
chrome.webNavigation.onBeforeNavigate.addListener(
|
||||
function (details) {
|
||||
// Note: pdfjs.action=download is not checked here because that code path
|
||||
// is not reachable for local files through the viewer when we do not have
|
||||
// file:-access.
|
||||
if (details.frameId === 0) {
|
||||
chrome.extension.isAllowedFileSchemeAccess(function (isAllowedAccess) {
|
||||
if (isAllowedAccess) {
|
||||
// Expected to be handled by DNR. Don't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.tabs.update(details.tabId, {
|
||||
url: getViewerURL(details.url),
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
url: [
|
||||
{
|
||||
urlPrefix: "file://",
|
||||
pathSuffix: ".pdf",
|
||||
},
|
||||
{
|
||||
urlPrefix: "file://",
|
||||
pathSuffix: ".PDF",
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
|
||||
if (message && message.action === "getParentOrigin") {
|
||||
// getParentOrigin is used to determine whether it is safe to embed a
|
||||
// sensitive (local) file in a frame.
|
||||
if (!sender.tab) {
|
||||
sendResponse("");
|
||||
return undefined;
|
||||
}
|
||||
// TODO: This should be the URL of the parent frame, not the tab. But
|
||||
// chrome-extension:-URLs are not visible in the webNavigation API
|
||||
// (https://crbug.com/326768), so the next best thing is using the tab's URL
|
||||
// for making security decisions.
|
||||
var parentUrl = sender.tab.url;
|
||||
if (!parentUrl) {
|
||||
sendResponse("");
|
||||
return undefined;
|
||||
}
|
||||
if (parentUrl.lastIndexOf("file:", 0) === 0) {
|
||||
sendResponse("file://");
|
||||
return undefined;
|
||||
}
|
||||
// The regexp should always match for valid URLs, but in case it doesn't,
|
||||
// just give the full URL (e.g. data URLs).
|
||||
var origin = /^[^:]+:\/\/[^/]+/.exec(parentUrl);
|
||||
sendResponse(origin ? origin[1] : parentUrl);
|
||||
return true;
|
||||
}
|
||||
if (message && message.action === "isAllowedFileSchemeAccess") {
|
||||
chrome.extension.isAllowedFileSchemeAccess(sendResponse);
|
||||
return true;
|
||||
}
|
||||
if (message && message.action === "openExtensionsPageForFileAccess") {
|
||||
var url = "chrome://extensions/?id=" + chrome.runtime.id;
|
||||
if (message.data.newTab) {
|
||||
chrome.tabs.create({
|
||||
windowId: sender.tab.windowId,
|
||||
index: sender.tab.index + 1,
|
||||
url,
|
||||
openerTabId: sender.tab.id,
|
||||
});
|
||||
} else {
|
||||
chrome.tabs.update(sender.tab.id, {
|
||||
url,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (message && message.action === "canRequestBody") {
|
||||
sendResponse(canRequestBody(sender.tab.id, sender.frameId));
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
159
extensions/chromium/preserve-referer.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright 2015 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
/**
|
||||
* This file is one part of the Referer persistency implementation. The other
|
||||
* part resides in chromecom.js.
|
||||
*
|
||||
* This file collects Referer headers for every http(s) request, and temporarily
|
||||
* stores the request headers in a dictionary, for REFERRER_IN_MEMORY_TIME ms.
|
||||
*
|
||||
* When the viewer is opened, it opens a port ("chromecom-referrer"). This port
|
||||
* is used to set up the webRequest listeners that stick the Referer headers to
|
||||
* the HTTP requests created by this extension. When the port is disconnected,
|
||||
* the webRequest listeners and the referrer information is discarded.
|
||||
*
|
||||
* See setReferer in chromecom.js for more explanation of this logic.
|
||||
*/
|
||||
|
||||
/* exported canRequestBody */ // Used in pdfHandler.js
|
||||
|
||||
// g_referrers[tabId][frameId] = referrer of PDF frame.
|
||||
var g_referrers = {};
|
||||
var g_referrerTimers = {};
|
||||
// The background script will eventually suspend after 30 seconds of inactivity.
|
||||
// This can be delayed when extension events are firing. To prevent the data
|
||||
// from being kept in memory for too long, cap the data duration to 5 minutes.
|
||||
var REFERRER_IN_MEMORY_TIME = 300000;
|
||||
|
||||
// g_postRequests[tabId] = Set of frameId that were loaded via POST.
|
||||
var g_postRequests = {};
|
||||
|
||||
var rIsReferer = /^referer$/i;
|
||||
chrome.webRequest.onSendHeaders.addListener(
|
||||
function saveReferer(details) {
|
||||
const { tabId, frameId, requestHeaders, method } = details;
|
||||
g_referrers[tabId] ??= {};
|
||||
g_referrers[tabId][frameId] = requestHeaders.find(h =>
|
||||
rIsReferer.test(h.name)
|
||||
)?.value;
|
||||
setCanRequestBody(tabId, frameId, method !== "GET");
|
||||
forgetReferrerEventually(tabId);
|
||||
},
|
||||
{ urls: ["*://*/*"], types: ["main_frame", "sub_frame"] },
|
||||
["requestHeaders", "extraHeaders"]
|
||||
);
|
||||
|
||||
function forgetReferrerEventually(tabId) {
|
||||
if (g_referrerTimers[tabId]) {
|
||||
clearTimeout(g_referrerTimers[tabId]);
|
||||
}
|
||||
g_referrerTimers[tabId] = setTimeout(() => {
|
||||
delete g_referrers[tabId];
|
||||
delete g_referrerTimers[tabId];
|
||||
delete g_postRequests[tabId];
|
||||
}, REFERRER_IN_MEMORY_TIME);
|
||||
}
|
||||
|
||||
// Keeps track of whether a document in tabId + frameId is loaded through a
|
||||
// POST form submission. Although this logic has nothing to do with referrer
|
||||
// tracking, it is still here to enable re-use of the webRequest listener above.
|
||||
function setCanRequestBody(tabId, frameId, isPOST) {
|
||||
if (isPOST) {
|
||||
g_postRequests[tabId] ??= new Set();
|
||||
g_postRequests[tabId].add(frameId);
|
||||
} else {
|
||||
g_postRequests[tabId]?.delete(frameId);
|
||||
}
|
||||
}
|
||||
|
||||
function canRequestBody(tabId, frameId) {
|
||||
// Returns true unless the frame is known to be loaded through a POST request.
|
||||
// If the background suspends, the information may be lost. This is acceptable
|
||||
// because the information is only potentially needed shortly after document
|
||||
// load, by contentscript.js.
|
||||
return !g_postRequests[tabId]?.has(frameId);
|
||||
}
|
||||
|
||||
// This method binds a webRequest event handler which adds the Referer header
|
||||
// to matching PDF resource requests (only if the Referer is non-empty). The
|
||||
// handler is removed as soon as the PDF viewer frame is unloaded.
|
||||
chrome.runtime.onConnect.addListener(function onReceivePort(port) {
|
||||
if (port.name !== "chromecom-referrer") {
|
||||
return;
|
||||
}
|
||||
var tabId = port.sender.tab.id;
|
||||
var frameId = port.sender.frameId;
|
||||
var dnrRequestId;
|
||||
|
||||
// If the PDF is viewed for the first time, then the referer will be set here.
|
||||
// Note: g_referrers could be empty if the background script was suspended by
|
||||
// the browser. In that case, chromecom.js may send us the referer (below).
|
||||
var referer = (g_referrers[tabId] && g_referrers[tabId][frameId]) || "";
|
||||
port.onMessage.addListener(function (data) {
|
||||
// If the viewer was opened directly (without opening a PDF URL first), then
|
||||
// the background script does not know about g_referrers, but the viewer may
|
||||
// know about the referer if stored in the history state (see chromecom.js).
|
||||
if (data.referer) {
|
||||
referer = data.referer;
|
||||
}
|
||||
dnrRequestId = data.dnrRequestId;
|
||||
setStickyReferrer(dnrRequestId, tabId, data.requestUrl, referer, () => {
|
||||
// Acknowledge the message, and include the latest referer for this frame.
|
||||
port.postMessage(referer);
|
||||
});
|
||||
});
|
||||
|
||||
// The port is only disconnected when the other end reloads.
|
||||
port.onDisconnect.addListener(function () {
|
||||
unsetStickyReferrer(dnrRequestId);
|
||||
});
|
||||
});
|
||||
|
||||
function setStickyReferrer(dnrRequestId, tabId, url, referer, callback) {
|
||||
if (!referer) {
|
||||
unsetStickyReferrer(dnrRequestId);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
const rule = {
|
||||
id: dnrRequestId,
|
||||
condition: {
|
||||
urlFilter: `|${url}|`,
|
||||
// The viewer and background are presumed to have the same origin:
|
||||
initiatorDomains: [location.hostname], // = chrome.runtime.id.
|
||||
resourceTypes: ["xmlhttprequest"],
|
||||
tabIds: [tabId],
|
||||
},
|
||||
action: {
|
||||
type: "modifyHeaders",
|
||||
requestHeaders: [{ operation: "set", header: "referer", value: referer }],
|
||||
},
|
||||
};
|
||||
chrome.declarativeNetRequest.updateSessionRules(
|
||||
{ removeRuleIds: [dnrRequestId], addRules: [rule] },
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
function unsetStickyReferrer(dnrRequestId) {
|
||||
if (dnrRequestId) {
|
||||
chrome.declarativeNetRequest.updateSessionRules({
|
||||
removeRuleIds: [dnrRequestId],
|
||||
});
|
||||
}
|
||||
}
|
||||
29
extensions/chromium/suppress-update.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2015 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Do not reload the extension when an update becomes available, UNLESS the PDF
|
||||
// viewer is not displaying any PDF files. Otherwise the tabs would close, which
|
||||
// is quite disruptive (crbug.com/511670).
|
||||
chrome.runtime.onUpdateAvailable.addListener(function () {
|
||||
chrome.tabs.query({ url: chrome.runtime.getURL("*") }, tabs => {
|
||||
if (tabs?.length) {
|
||||
return;
|
||||
}
|
||||
chrome.runtime.reload();
|
||||
});
|
||||
});
|
||||
187
extensions/chromium/telemetry.js
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2016 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/* eslint strict: ["error", "function"] */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
// This module sends the browser and extension version to a server, to
|
||||
// determine whether it is safe to drop support for old Chrome versions in
|
||||
// future extension updates.
|
||||
//
|
||||
// The source code for the server is available at:
|
||||
// https://github.com/Rob--W/pdfjs-telemetry
|
||||
var LOG_URL = "https://pdfjs.robwu.nl/logpdfjs";
|
||||
|
||||
// The minimum time to wait before sending a ping, so that we don't send too
|
||||
// many requests even if the user restarts their browser very often.
|
||||
// We want one ping a day, so a minimum delay of 12 hours should be OK.
|
||||
var MINIMUM_TIME_BETWEEN_PING = 12 * 36e5;
|
||||
|
||||
if (chrome.extension.inIncognitoContext) {
|
||||
// The extension uses incognito split mode, so there are two background
|
||||
// pages. Only send telemetry when not in incognito mode.
|
||||
return;
|
||||
}
|
||||
|
||||
if (chrome.runtime.id !== "oemmndcbldboiebfnladdacbdfmadadm") {
|
||||
// Only send telemetry for the official PDF.js extension.
|
||||
console.warn("Disabled telemetry because this is not an official build.");
|
||||
return;
|
||||
}
|
||||
|
||||
// The localStorage API is unavailable in service workers. We store data in
|
||||
// chrome.storage.local and use this "localStorage" object to enable
|
||||
// synchronous access in the logic.
|
||||
const localStorage = {
|
||||
telemetryLastTime: 0,
|
||||
telemetryDeduplicationId: "",
|
||||
telemetryLastVersion: "",
|
||||
};
|
||||
|
||||
chrome.alarms.onAlarm.addListener(alarm => {
|
||||
if (alarm.name === "maybeSendPing") {
|
||||
maybeSendPing();
|
||||
}
|
||||
});
|
||||
chrome.storage.session.get({ didPingCheck: false }, async items => {
|
||||
if (items?.didPingCheck) {
|
||||
return;
|
||||
}
|
||||
maybeSendPing();
|
||||
await chrome.alarms.clear("maybeSendPing");
|
||||
await chrome.alarms.create("maybeSendPing", { periodInMinutes: 60 });
|
||||
chrome.storage.session.set({ didPingCheck: true });
|
||||
});
|
||||
|
||||
function updateLocalStorage(key, value) {
|
||||
localStorage[key] = value;
|
||||
// Note: We mirror the data in localStorage because the following is async.
|
||||
chrome.storage.local.set({ [key]: value });
|
||||
}
|
||||
|
||||
function maybeSendPing() {
|
||||
getLoggingPref(function (didOptOut) {
|
||||
if (didOptOut) {
|
||||
// Respect the user's decision to not send statistics.
|
||||
return;
|
||||
}
|
||||
if (!navigator.onLine) {
|
||||
// No network available; Wait until the next scheduled ping opportunity.
|
||||
// Even if onLine is true, the server may still be unreachable. But
|
||||
// because it is impossible to tell whether a request failed due to the
|
||||
// inability to connect, or a deliberate connection termination by the
|
||||
// server, we don't validate the response and assume that the request
|
||||
// succeeded. This ensures that the server cannot ask the client to
|
||||
// send more pings.
|
||||
return;
|
||||
}
|
||||
doSendPing();
|
||||
});
|
||||
}
|
||||
|
||||
function doSendPing() {
|
||||
chrome.storage.local.get(localStorage, items => {
|
||||
Object.assign(localStorage, items);
|
||||
|
||||
var lastTime = parseInt(localStorage.telemetryLastTime, 10) || 0;
|
||||
var wasUpdated = didUpdateSinceLastCheck();
|
||||
if (!wasUpdated && Date.now() - lastTime < MINIMUM_TIME_BETWEEN_PING) {
|
||||
return;
|
||||
}
|
||||
updateLocalStorage("telemetryLastTime", Date.now());
|
||||
|
||||
var deduplication_id = getDeduplicationId(wasUpdated);
|
||||
var extension_version = chrome.runtime.getManifest().version;
|
||||
fetch(LOG_URL, {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"Deduplication-Id": deduplication_id,
|
||||
"Extension-Version": extension_version,
|
||||
}),
|
||||
// Set mode=cors so that the above custom headers are included in the
|
||||
// request.
|
||||
mode: "cors",
|
||||
// Omits credentials such as cookies in the requests, which guarantees
|
||||
// that the server cannot track the client via HTTP cookies.
|
||||
credentials: "omit",
|
||||
cache: "no-store",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 40-bit hexadecimal string (=10 letters, 1.1E12 possibilities).
|
||||
* This is used by the server to discard duplicate entries of the same browser
|
||||
* version when the log data is aggregated.
|
||||
*/
|
||||
function getDeduplicationId(wasUpdated) {
|
||||
var id = localStorage.telemetryDeduplicationId;
|
||||
// The ID is only used to deduplicate reports for the same browser version,
|
||||
// so it is OK to change the ID if the browser is updated. By changing the
|
||||
// ID, the server cannot track users for a long period even if it wants to.
|
||||
if (!id || !/^[0-9a-f]{10}$/.test(id) || wasUpdated) {
|
||||
id = "";
|
||||
var buf = new Uint8Array(5);
|
||||
crypto.getRandomValues(buf);
|
||||
for (const c of buf) {
|
||||
id += (c >>> 4).toString(16) + (c & 0xf).toString(16);
|
||||
}
|
||||
updateLocalStorage("telemetryDeduplicationId", id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the browser has received a major update since the last call
|
||||
* to this function.
|
||||
*/
|
||||
function didUpdateSinceLastCheck() {
|
||||
var chromeVersion = /Chrome\/(\d+)\./.exec(navigator.userAgent);
|
||||
chromeVersion = chromeVersion && chromeVersion[1];
|
||||
if (!chromeVersion || localStorage.telemetryLastVersion === chromeVersion) {
|
||||
return false;
|
||||
}
|
||||
updateLocalStorage("telemetryLastVersion", chromeVersion);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the telemetry preference. The callback is invoked with a
|
||||
* boolean if a preference is found, and with the undefined value otherwise.
|
||||
*/
|
||||
function getLoggingPref(callback) {
|
||||
// Try to look up the preference in the storage, in the following order:
|
||||
var areas = ["sync", "local", "managed"];
|
||||
|
||||
next();
|
||||
function next(result) {
|
||||
var storageAreaName = areas.shift();
|
||||
if (typeof result === "boolean" || !storageAreaName) {
|
||||
callback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chrome.storage[storageAreaName]) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage[storageAreaName].get("disableTelemetry", function (items) {
|
||||
next(items && items.disableTelemetry);
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
1
external/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cmaps/
|
||||
1
external/bcmaps/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* binary
|
||||