├── .travis.yml ├── test ├── test.pdf ├── index.html ├── test.less ├── webpack.config.js ├── package.json └── test.jsx ├── .eslintignore ├── sample ├── sample.pdf ├── index.html ├── sample.less ├── webpack.config.js ├── package.json └── sample.jsx ├── .gitignore ├── src ├── react-pdf.entry.js ├── react-pdf.entry.noworker.js └── react-pdf.jsx ├── .babelrc ├── .eslintrc ├── .codeclimate.yml ├── .gitattributes ├── README.md ├── LICENSE └── package.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "node" 3 | -------------------------------------------------------------------------------- /test/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnarhinen/react-pdf/HEAD/test/test.pdf -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled files 2 | build/* 3 | sample/build/* 4 | test/build/* 5 | -------------------------------------------------------------------------------- /sample/sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnarhinen/react-pdf/HEAD/sample/sample.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | sample/build 3 | test/build 4 | node_modules 5 | npm-debug.log 6 | package-lock.json 7 | stats*.json 8 | -------------------------------------------------------------------------------- /src/react-pdf.entry.js: -------------------------------------------------------------------------------- 1 | const ReactPDF = require('./react-pdf'); 2 | 3 | require('pdfjs-dist/webpack'); 4 | require('pdfjs-dist/web/compatibility'); 5 | 6 | module.exports = ReactPDF; 7 | -------------------------------------------------------------------------------- /src/react-pdf.entry.noworker.js: -------------------------------------------------------------------------------- 1 | const ReactPDF = require('./react-pdf'); 2 | 3 | const pdfjs = require('pdfjs-dist'); 4 | require('pdfjs-dist/web/compatibility'); 5 | 6 | pdfjs.PDFJS.disableWorker = true; 7 | 8 | module.exports = ReactPDF; 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "stage-2", 10 | "react" 11 | ], 12 | "plugins": [ 13 | "transform-class-properties" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-pdf sample page 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-pdf test page 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "PDFJS": true 9 | }, 10 | "parser": "babel-eslint", 11 | "parserOptions": { 12 | "ecmaFeatures": { 13 | "experimentalObjectRestSpread": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | exclude_paths: 5 | - "webpack.config.js" 6 | - "test/webpack.config.js" 7 | - "sample/webpack.config.js" 8 | config: 9 | languages: 10 | - javascript 11 | eslint: 12 | enabled: true 13 | fixme: 14 | enabled: true 15 | ratings: 16 | paths: 17 | - "**.js" 18 | - "**.jsx" 19 | exclude_paths: 20 | - build/ 21 | - test/build/ 22 | - sample/build/ 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-PDF 2 | Easily display PDF files in your React application. 3 | 4 | # We've moved 5 | 6 | This GitHub repository is no longer maintained. See [wojtekmaj/react-pdf](https://github.com/wojtekmaj/react-pdf) to report bugs, contribute and follow existing and upcoming releases. 7 | 8 | Don't worry, react-pdf on NPM is fully up-to-date with [wojtekmaj/react-pdf](https://github.com/wojtekmaj/react-pdf). You don't need to do any changes within your package.json file! 9 | -------------------------------------------------------------------------------- /sample/sample.less: -------------------------------------------------------------------------------- 1 | .Example { 2 | font-family: Segoe UI, Tahoma, sans-serif; 3 | 4 | input, button { 5 | font: inherit; 6 | } 7 | 8 | &__container { 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | 13 | &__load { 14 | } 15 | 16 | &__preview { 17 | border: 1px solid darkgray; 18 | margin: 1em 0; 19 | } 20 | 21 | &__controls { 22 | width: 300px; 23 | display: flex; 24 | 25 | span { 26 | flex-grow: 1; 27 | margin: 0 1em; 28 | text-align: center; 29 | } 30 | 31 | button { 32 | width: 80px; 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /test/test.less: -------------------------------------------------------------------------------- 1 | .Example { 2 | font-family: Segoe UI, Tahoma, sans-serif; 3 | 4 | input, button { 5 | font: inherit; 6 | } 7 | 8 | &__container { 9 | display: flex; 10 | flex-direction: row; 11 | align-items: flex-start; 12 | 13 | &__load { 14 | width: 420px; 15 | } 16 | 17 | &__preview { 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | width: 420px; 22 | 23 | &__out { 24 | border: 1px solid darkgray; 25 | margin: 1em 0; 26 | } 27 | 28 | &__controls { 29 | width: 420px; 30 | display: flex; 31 | 32 | span { 33 | flex-grow: 1; 34 | margin: 0 1em; 35 | text-align: center; 36 | } 37 | 38 | button { 39 | width: 80px; 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 3 | 4 | module.exports = { 5 | context: __dirname, 6 | devtool: 'source-map', 7 | entry: './test', 8 | output: { 9 | path: path.join(__dirname, 'build'), 10 | filename: '[name].bundle.js', 11 | }, 12 | resolve: { 13 | extensions: ['.js', '.jsx'], 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.pdf$/, 19 | use: 'url-loader', 20 | }, 21 | { 22 | test: /\.less$/, 23 | use: [ 24 | 'style-loader', 25 | 'css-loader', 26 | 'less-loader', 27 | ], 28 | }, 29 | { 30 | test: /\.jsx?$/, 31 | exclude: /node_modules/, 32 | use: 'babel-loader', 33 | }, 34 | ], 35 | }, 36 | plugins: [ 37 | new CopyWebpackPlugin([ 38 | { from: './index.html' }, 39 | { from: './test.pdf' }, 40 | ]), 41 | ], 42 | }; 43 | -------------------------------------------------------------------------------- /sample/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | context: __dirname, 7 | entry: './sample', 8 | output: { 9 | path: path.join(__dirname, 'build'), 10 | filename: '[name].bundle.js', 11 | }, 12 | resolve: { 13 | extensions: ['.js', '.jsx'], 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.less$/, 19 | use: [ 20 | 'style-loader', 21 | 'css-loader', 22 | 'less-loader', 23 | ], 24 | }, 25 | { 26 | test: /\.jsx?$/, 27 | exclude: /node_modules/, 28 | use: 'babel-loader', 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new webpack.DefinePlugin({ 34 | 'process.env': { 35 | NODE_ENV: JSON.stringify('production'), 36 | }, 37 | }), 38 | new webpack.optimize.UglifyJsPlugin(), 39 | new CopyWebpackPlugin([ 40 | { from: './index.html' }, 41 | { from: './sample.pdf' }, 42 | ]), 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Wojciech Maj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pdf-test-page", 3 | "version": "1.7.0", 4 | "description": "A test page for React-PDF.", 5 | "scripts": { 6 | "build": "webpack" 7 | }, 8 | "author": { 9 | "name": "Wojciech Maj", 10 | "email": "kontakt@wojtekmaj.pl" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "react": ">=15.5", 15 | "react-dom": ">=15.5", 16 | "react-pdf": "../" 17 | }, 18 | "devDependencies": { 19 | "babel-core": "^6.25.0", 20 | "babel-eslint": "^7.2.3", 21 | "babel-loader": "^7.1.1", 22 | "babel-plugin-transform-class-properties": "^6.24.1", 23 | "babel-preset-es2015": "^6.24.1", 24 | "babel-preset-react": "^6.24.1", 25 | "babel-preset-stage-2": "^6.24.1", 26 | "copy-webpack-plugin": "^4.0.1", 27 | "css-loader": "latest", 28 | "eslint": "^3.19.0", 29 | "eslint-config-airbnb": "^15.0.2", 30 | "eslint-plugin-class-property": "^1.0.6", 31 | "eslint-plugin-import": "^2.7.0", 32 | "eslint-plugin-jsx-a11y": "^5.1.1", 33 | "eslint-plugin-react": "^7.1.0", 34 | "file-loader": "latest", 35 | "less": "^2.7.2", 36 | "less-loader": "latest", 37 | "style-loader": "latest", 38 | "url-loader": "latest", 39 | "webpack": "^2.7.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pdf-sample-page", 3 | "version": "1.7.0", 4 | "description": "A sample page for React-PDF.", 5 | "scripts": { 6 | "build": "webpack" 7 | }, 8 | "author": { 9 | "name": "Wojciech Maj", 10 | "email": "kontakt@wojtekmaj.pl" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "react": ">=15.5", 15 | "react-dom": ">=15.5", 16 | "react-pdf": "../" 17 | }, 18 | "devDependencies": { 19 | "babel-core": "^6.25.0", 20 | "babel-eslint": "^7.2.3", 21 | "babel-loader": "^7.1.1", 22 | "babel-plugin-transform-class-properties": "^6.24.1", 23 | "babel-preset-es2015": "^6.24.1", 24 | "babel-preset-react": "^6.24.1", 25 | "babel-preset-stage-2": "^6.24.1", 26 | "copy-webpack-plugin": "^4.0.1", 27 | "css-loader": "latest", 28 | "eslint": "^3.19.0", 29 | "eslint-config-airbnb": "^15.0.2", 30 | "eslint-plugin-class-property": "^1.0.6", 31 | "eslint-plugin-import": "^2.7.0", 32 | "eslint-plugin-jsx-a11y": "^5.1.1", 33 | "eslint-plugin-react": "^7.1.0", 34 | "file-loader": "latest", 35 | "less": "^2.7.2", 36 | "less-loader": "latest", 37 | "style-loader": "latest", 38 | "url-loader": "latest", 39 | "webpack": "^2.7.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pdf", 3 | "version": "1.7.0", 4 | "description": "Easily display PDF files in your React application.", 5 | "main": "build/react-pdf.entry.js", 6 | "es6": "src/react-pdf.entry.js", 7 | "scripts": { 8 | "build": "babel src -d build", 9 | "eslint": "eslint ./src", 10 | "prepublishOnly": "npm run build", 11 | "test": "npm run eslint" 12 | }, 13 | "keywords": [ 14 | "pdf", 15 | "pdf-viewer", 16 | "react" 17 | ], 18 | "author": { 19 | "name": "Wojciech Maj", 20 | "email": "kontakt@wojtekmaj.pl" 21 | }, 22 | "contributors": [ 23 | { 24 | "name": "Niklas Närhinen", 25 | "email": "niklas@narhinen.net" 26 | }, 27 | { 28 | "name": "Bart Van Houtte", 29 | "email": "bart.van.houtte@ading.be" 30 | } 31 | ], 32 | "license": "MIT", 33 | "dependencies": { 34 | "pdfjs-dist": "^1.8.532", 35 | "prop-types": ">=15.5", 36 | "react": ">=15.5", 37 | "react-dom": ">=15.5" 38 | }, 39 | "devDependencies": { 40 | "babel-cli": "^6.24.1", 41 | "babel-core": "^6.25.0", 42 | "babel-eslint": "^7.2.3", 43 | "babel-plugin-transform-class-properties": "^6.24.1", 44 | "babel-preset-es2015": "^6.24.1", 45 | "babel-preset-react": "^6.24.1", 46 | "babel-preset-stage-2": "^6.24.1", 47 | "eslint": "^3.19.0", 48 | "eslint-config-airbnb": "^15.0.2", 49 | "eslint-plugin-class-property": "^1.0.6", 50 | "eslint-plugin-import": "^2.7.0", 51 | "eslint-plugin-jsx-a11y": "^5.1.1", 52 | "eslint-plugin-react": "^7.1.0" 53 | }, 54 | "repository": { 55 | "type": "git", 56 | "url": "https://github.com/wojtekmaj/react-pdf.git" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/sample.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import ReactPDF from 'react-pdf'; 4 | 5 | import './sample.less'; 6 | 7 | class Example extends Component { 8 | state = { 9 | file: './sample.pdf', 10 | pageIndex: null, 11 | pageNumber: null, 12 | total: null, 13 | } 14 | 15 | onFileChange = (event) => { 16 | this.setState({ 17 | file: event.target.files[0], 18 | }); 19 | } 20 | 21 | onDocumentLoad = ({ total }) => { 22 | this.setState({ total }); 23 | } 24 | 25 | onPageLoad = ({ pageIndex, pageNumber }) => { 26 | this.setState({ pageIndex, pageNumber }); 27 | } 28 | 29 | changePage(by) { 30 | this.setState(prevState => ({ 31 | pageIndex: prevState.pageIndex + by, 32 | })); 33 | } 34 | 35 | render() { 36 | const { file, pageIndex, pageNumber, total } = this.state; 37 | 38 | return ( 39 |
40 |

react-pdf sample page

41 |
42 |
43 |   44 | 48 |
49 |
50 | 57 |
58 |
59 | 65 | Page {pageNumber || '--'} of {total || '--'} 66 | 72 |
73 |
74 |
75 | ); 76 | } 77 | } 78 | 79 | render(, document.getElementById('react-container')); 80 | -------------------------------------------------------------------------------- /test/test.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import ReactPDF from 'react-pdf'; 4 | 5 | import './test.less'; 6 | 7 | import samplePDF from './test.pdf'; 8 | 9 | let componentRenderCount = 0; 10 | 11 | class WrappedReactPDF extends ReactPDF { 12 | componentDidMount() { 13 | super.componentDidMount(); 14 | } 15 | 16 | componentWillReceiveProps(nextProps) { 17 | super.componentWillReceiveProps(nextProps); 18 | } 19 | 20 | componentWillUpdate() { 21 | componentRenderCount += 1; 22 | } 23 | } 24 | 25 | WrappedReactPDF.propTypes = ReactPDF.propTypes; 26 | 27 | class Test extends Component { 28 | state = { 29 | file: null, 30 | pageIndex: null, 31 | pageNumber: null, 32 | passObj: false, 33 | pageRenderCount: 0, 34 | pageWidth: 300, 35 | total: null, 36 | } 37 | 38 | onFileChange = (event) => { 39 | this.setState({ 40 | file: event.target.files[0], 41 | }); 42 | } 43 | 44 | onFileUintChange = (event) => { 45 | const reader = new FileReader(); 46 | 47 | reader.onloadend = () => { 48 | this.setState({ 49 | file: reader.result, 50 | }); 51 | }; 52 | 53 | reader.readAsArrayBuffer(event.target.files[0]); 54 | } 55 | 56 | onURLChange = (event) => { 57 | event.preventDefault(); 58 | 59 | const url = event.target.querySelector('input').value; 60 | 61 | if (!url) { 62 | return; 63 | } 64 | 65 | this.setState({ 66 | file: url, 67 | }); 68 | } 69 | 70 | onRequestChange = (event) => { 71 | event.preventDefault(); 72 | 73 | const url = event.target.querySelector('input').value; 74 | 75 | if (!url) { 76 | return; 77 | } 78 | 79 | fetch(url).then(response => response.blob()).then((blob) => { 80 | this.setState({ 81 | file: blob, 82 | }); 83 | }); 84 | } 85 | 86 | onUseImported = () => { 87 | this.setState({ 88 | file: samplePDF, 89 | }); 90 | } 91 | 92 | onPassObjChange = (event) => { 93 | this.setState({ passObj: event.target.checked }); 94 | } 95 | 96 | onPageWidthChange = (event) => { 97 | const width = event.target.value; 98 | 99 | if (!width) { 100 | return; 101 | } 102 | 103 | this.setState({ 104 | pageWidth: parseInt(width, 10), 105 | }); 106 | } 107 | 108 | onDocumentLoad = ({ total }) => { 109 | this.setState({ total }); 110 | } 111 | 112 | onDocumentError = ({ message }) => { 113 | // eslint-disable-next-line no-console 114 | console.error(message); 115 | } 116 | 117 | onPageLoad = ({ pageIndex, pageNumber }) => { 118 | this.setState({ pageIndex, pageNumber }); 119 | } 120 | 121 | onPageRender = () => { 122 | this.setState({ pageRenderCount: (this.state.pageRenderCount + 1) }); 123 | } 124 | 125 | get transformedFile() { 126 | if (!this.state.passObj) { 127 | return this.state.file; 128 | } 129 | 130 | const result = {}; 131 | if (typeof this.state.file === 'string') { 132 | result.url = this.state.file; 133 | } else { 134 | return this.state.file; 135 | } 136 | return result; 137 | } 138 | 139 | changePage(by) { 140 | this.setState(prevState => ({ 141 | pageIndex: prevState.pageIndex + by, 142 | })); 143 | } 144 | 145 | render() { 146 | const { pageIndex, pageNumber, pageRenderCount, pageWidth, total } = this.state; 147 | 148 | return ( 149 |
150 |

react-pdf test page

151 |
152 |
153 |   154 | 158 |

159 |   160 | 164 |

165 |
166 |   167 | 168 | 169 |
170 |
171 |
172 |   173 | 174 | 175 |
176 |
177 | 178 |

179 | 180 | 181 |
182 |
183 |
184 |   185 | 190 |
191 |
192 |
193 |
194 |
195 | 203 |
204 |
205 | 211 | Page {pageNumber || '--'} of {total || '--'} 212 | 218 |
219 |
220 | Page render count: {pageRenderCount}
221 | Component render count: {componentRenderCount} 222 |
223 |
224 |
225 |
226 | ); 227 | } 228 | } 229 | 230 | render(, document.getElementById('react-container')); 231 | -------------------------------------------------------------------------------- /src/react-pdf.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class ReactPDF extends Component { 5 | state = { 6 | pdf: null, 7 | page: null, 8 | } 9 | 10 | componentDidMount() { 11 | this.handleFileLoad(); 12 | } 13 | 14 | componentWillReceiveProps(nextProps) { 15 | if (this.isParameterObject(nextProps.file)) { 16 | // File is a parameter object 17 | if ( 18 | (nextProps.file && !this.props.file) || 19 | nextProps.file.data !== this.props.file.data || 20 | nextProps.file.range !== this.props.file.range || 21 | nextProps.file.url !== this.props.file.url 22 | ) { 23 | this.handleFileLoad(nextProps); 24 | return; 25 | } 26 | } else if (nextProps.file && nextProps.file !== this.props.file) { 27 | // File is a normal object or not an object at all 28 | this.handleFileLoad(nextProps); 29 | return; 30 | } 31 | 32 | if ( 33 | this.state.pdf && 34 | typeof nextProps.pageIndex !== 'undefined' && 35 | nextProps.pageIndex !== this.props.pageIndex 36 | ) { 37 | this.loadPage(nextProps.pageIndex); 38 | } 39 | } 40 | 41 | shouldComponentUpdate(nextProps, nextState) { 42 | return ( 43 | nextState.pdf !== this.state.pdf || 44 | nextState.page !== this.state.page || 45 | nextProps.width !== this.props.width || 46 | nextProps.scale !== this.props.scale 47 | ); 48 | } 49 | 50 | /** 51 | * Called when a document is loaded successfully. 52 | */ 53 | onDocumentLoad = (pdf) => { 54 | this.callIfDefined( 55 | this.props.onDocumentLoad, 56 | { 57 | total: pdf.numPages, 58 | }, 59 | ); 60 | 61 | this.setState({ pdf }); 62 | 63 | this.loadPage(this.props.pageIndex); 64 | } 65 | 66 | /** 67 | * Called when a document fails to load. 68 | */ 69 | onDocumentError = (error) => { 70 | this.callIfDefined( 71 | this.props.onDocumentError, 72 | error, 73 | ); 74 | 75 | this.setState({ pdf: false }); 76 | } 77 | 78 | /** 79 | * Called when a page is loaded successfully. 80 | */ 81 | onPageLoad = (page) => { 82 | const scale = this.getPageScale(page); 83 | 84 | this.callIfDefined( 85 | this.props.onPageLoad, 86 | { 87 | pageIndex: page.pageIndex, 88 | pageNumber: page.pageNumber, 89 | get width() { return page.view[2] * scale; }, 90 | get height() { return page.view[3] * scale; }, 91 | scale, 92 | get originalWidth() { return page.view[2]; }, 93 | get originalHeight() { return page.view[3]; }, 94 | }, 95 | ); 96 | 97 | this.setState({ page }); 98 | } 99 | 100 | /** 101 | * Called when a page is rendered successfully. 102 | */ 103 | onPageRender = () => { 104 | this.renderer = null; 105 | 106 | this.callIfDefined(this.props.onPageRender); 107 | } 108 | 109 | /** 110 | * Called when a page fails to load or render. 111 | */ 112 | onPageError = (error) => { 113 | this.callIfDefined( 114 | this.props.onPageError, 115 | error, 116 | ); 117 | 118 | this.setState({ page: false }); 119 | } 120 | 121 | getPageScale(page = this.state.page) { 122 | const { scale, width } = this.props; 123 | 124 | // Be default, we'll render page at 100% * scale width. 125 | let pageScale = 1; 126 | 127 | // If width is defined, calculate the scale of the page so it could be of desired width. 128 | if (width) { 129 | pageScale = width / page.getViewport(scale).width; 130 | } 131 | 132 | return scale * pageScale; 133 | } 134 | 135 | callIfDefined = (fn, args) => { 136 | if (fn && typeof fn === 'function') { 137 | fn(args); 138 | } 139 | } 140 | 141 | displayCORSWarning = () => { 142 | // eslint-disable-next-line no-console 143 | console.warn('Loading PDF as base64 strings/URLs might not work on protocols other than HTTP/HTTPS. On Google Chrome, you can use --allow-file-access-from-files flag for debugging purposes.'); 144 | } 145 | 146 | isParameterObject = object => 147 | object && 148 | typeof object === 'object' && 149 | ['file', 'range', 'url'].some(key => Object.keys(object).includes(key)) 150 | 151 | isDataURI = str => /^data:/.test(str) 152 | 153 | dataURItoBlob = (dataURI) => { 154 | let byteString; 155 | if (dataURI.split(',')[0].indexOf('base64') >= 0) { 156 | byteString = atob(dataURI.split(',')[1]); 157 | } else { 158 | byteString = unescape(dataURI.split(',')[1]); 159 | } 160 | 161 | const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 162 | 163 | const ia = new Uint8Array(byteString.length); 164 | for (let i = 0; i < byteString.length; i += 1) { 165 | ia[i] = byteString.charCodeAt(i); 166 | } 167 | 168 | return new Blob([ia], { type: mimeString }); 169 | } 170 | 171 | handleFileLoad(props = this.props) { 172 | let { file } = props; 173 | 174 | if ( 175 | !file || 176 | ( 177 | this.isParameterObject(file) && 178 | !file.data && !file.range && !file.url 179 | ) 180 | ) { 181 | return null; 182 | } 183 | 184 | this.setState({ 185 | page: null, 186 | pdf: null, 187 | }); 188 | 189 | // File is a string 190 | if (typeof file === 'string') { 191 | // File is not data URI 192 | if (!this.isDataURI(file)) { 193 | if (window.location.protocol === 'file:') { 194 | this.displayCORSWarning(); 195 | } 196 | 197 | return this.loadDocument(file); 198 | } 199 | 200 | // File is data URI 201 | file = this.dataURItoBlob(file); 202 | 203 | // Fall through to "File is a blob" 204 | } 205 | 206 | // File is a Blob 207 | if (file instanceof Blob) { 208 | file = URL.createObjectURL(file); 209 | 210 | return this.loadDocument(file); 211 | } 212 | 213 | // File is a File 214 | if (file instanceof File) { 215 | const reader = new FileReader(); 216 | 217 | reader.onloadend = () => { 218 | this.loadDocument(new Uint8Array(reader.result)); 219 | }; 220 | 221 | return reader.readAsArrayBuffer(file); 222 | } 223 | 224 | // File is an ArrayBuffer 225 | if (file instanceof ArrayBuffer) { 226 | return this.loadDocument(file); 227 | } 228 | 229 | // File is a parameter object 230 | if (this.isParameterObject(file)) { 231 | if ( 232 | file.url && 233 | window.location.protocol === 'file:' 234 | ) { 235 | this.displayCORSWarning(); 236 | } 237 | 238 | // Prevent from modifying props 239 | file = Object.assign({}, file); 240 | 241 | // File is data URI 242 | if (file.url && this.isDataURI(file.url)) { 243 | file = URL.createObjectURL(this.dataURItoBlob(file.url)); 244 | } 245 | 246 | return this.loadDocument(file); 247 | } 248 | 249 | throw new Error('Unrecognized input type.'); 250 | } 251 | 252 | loadDocument(...args) { 253 | PDFJS.getDocument(...args) 254 | .then(this.onDocumentLoad) 255 | .catch(this.onDocumentError); 256 | } 257 | 258 | loadPage(pageIndex) { 259 | const { pdf } = this.state; 260 | 261 | if (!pdf) { 262 | throw new Error('Unexpected call to getPage() before the document has been loaded.'); 263 | } 264 | 265 | let pageNumber = pageIndex + 1; 266 | 267 | if (!pageIndex || pageNumber < 1) { 268 | pageNumber = 1; 269 | } else if (pageNumber >= pdf.numPages) { 270 | pageNumber = pdf.numPages; 271 | } 272 | 273 | pdf.getPage(pageNumber) 274 | .then(this.onPageLoad) 275 | .catch(this.onPageError); 276 | } 277 | 278 | renderNoData() { 279 | return ( 280 |
{this.props.noData}
281 | ); 282 | } 283 | 284 | renderError() { 285 | return ( 286 |
{this.props.error}
287 | ); 288 | } 289 | 290 | renderLoader() { 291 | return ( 292 |
{this.props.loading}
293 | ); 294 | } 295 | 296 | render() { 297 | const { file } = this.props; 298 | const { pdf, page } = this.state; 299 | 300 | if (!file) { 301 | return this.renderNoData(); 302 | } 303 | 304 | if (pdf === false || page === false) { 305 | return this.renderError(); 306 | } 307 | 308 | if (pdf === null || page === null) { 309 | return this.renderLoader(); 310 | } 311 | 312 | return ( 313 | { 315 | if (!ref) return; 316 | 317 | const canvas = ref; 318 | 319 | const pixelRatio = window.devicePixelRatio || 1; 320 | const viewport = page.getViewport(this.getPageScale() * pixelRatio); 321 | 322 | canvas.height = viewport.height; 323 | canvas.width = viewport.width; 324 | 325 | canvas.style.height = `${viewport.height / pixelRatio}px`; 326 | canvas.style.width = `${viewport.width / pixelRatio}px`; 327 | 328 | const canvasContext = canvas.getContext('2d'); 329 | 330 | const renderContext = { 331 | canvasContext, 332 | viewport, 333 | }; 334 | 335 | // If another render is in progress, let's cancel it 336 | /* eslint-disable no-underscore-dangle */ 337 | if (this.renderer && this.renderer._internalRenderTask.running) { 338 | this.renderer._internalRenderTask.cancel(); 339 | } 340 | /* eslint-enable no-underscore-dangle */ 341 | 342 | this.renderer = page.render(renderContext); 343 | 344 | this.renderer 345 | .then(this.onPageRender) 346 | .catch((dismiss) => { 347 | if (dismiss === 'cancelled') { 348 | // Everything's alright 349 | return; 350 | } 351 | 352 | this.onPageError(dismiss); 353 | }); 354 | }} 355 | /> 356 | ); 357 | } 358 | } 359 | 360 | ReactPDF.defaultProps = { 361 | error: 'Failed to load PDF file.', 362 | loading: 'Loading PDF…', 363 | noData: 'No PDF file specified.', 364 | pageIndex: 0, 365 | scale: 1.0, 366 | }; 367 | 368 | ReactPDF.propTypes = { 369 | error: PropTypes.oneOfType([ 370 | PropTypes.string, 371 | PropTypes.node, 372 | ]), 373 | file: PropTypes.oneOfType([ 374 | PropTypes.string, 375 | PropTypes.instanceOf(File), 376 | PropTypes.instanceOf(Blob), 377 | PropTypes.shape({ 378 | data: PropTypes.object, 379 | httpHeaders: PropTypes.object, 380 | range: PropTypes.object, 381 | url: PropTypes.string, 382 | withCredentials: PropTypes.bool, 383 | }), 384 | ]), 385 | loading: PropTypes.oneOfType([ 386 | PropTypes.string, 387 | PropTypes.node, 388 | ]), 389 | noData: PropTypes.oneOfType([ 390 | PropTypes.string, 391 | PropTypes.node, 392 | ]), 393 | onDocumentError: PropTypes.func, 394 | onDocumentLoad: PropTypes.func, 395 | onPageError: PropTypes.func, 396 | onPageLoad: PropTypes.func, 397 | onPageRender: PropTypes.func, 398 | pageIndex: PropTypes.number, 399 | scale: PropTypes.number, 400 | width: PropTypes.number, 401 | }; 402 | --------------------------------------------------------------------------------