Reputation: 21
Does Expo have a way to view a PDF and jump to a specific page in said PDF. I've looked into https://www.npmjs.com/package/react-native-pdf but i found it to be incompatible with expo given its use of native React-Native libraries. I've also tried https://github.com/xcarpentier/rn-pdf-reader-js, but there appears to be a couple of issues with it and it doesn't look like the creator updates it anymore.
Upvotes: 2
Views: 4639
Reputation: 1
Installed the following library: 1 - Expo constant 2 - Expo File system
Use the below component:
import React, { useState } from 'react';
import { useEffect } from 'react';
import {
View,
Platform,
StyleSheet,
Alert
} from 'react-native';
import * as FileSystem from 'expo-file-system';
import { WebView } from 'react-native-webview';
import Loader from '../Loader/Loader';
const {
cacheDirectory,
writeAsStringAsync,
deleteAsync,
getInfoAsync,
EncodingType,
} = FileSystem
const PdfReader = (props) => {
const isAndroid = Platform.OS == 'android' ? true : false;
const [renderType, setRenderType] = useState(undefined);
const [ready, setReady] = useState(false);
const [renderedOnce, setRenderedOnce] = useState(undefined);
const bundleJsPath = `${cacheDirectory}bundle.js`
const htmlPath = `${cacheDirectory}index.html`
const pdfPath = `${cacheDirectory}file.pdf`
const getRenderType = () => {
const {
useGoogleReader,
useGoogleDriveViewer,
source: { uri, base64 },
} = props
if (useGoogleReader) {
return 'GOOGLE_READER'
}
if (useGoogleDriveViewer) {
return 'GOOGLE_DRIVE_VIEWER';
}
if (Platform.OS === 'ios') {
if (uri !== undefined) {
return 'DIRECT_URL'
}
if (base64 !== undefined) {
return 'BASE64_TO_LOCAL_PDF'
}
}
if (base64 !== undefined) {
return 'DIRECT_BASE64'
}
if (uri !== undefined) {
return 'URL_TO_BASE64'
}
return undefined
}
const validate = () => {
let renderType_n = renderType == undefined ? getRenderType() : renderType;
const {source } = props;
if (!renderType_n || !source) {
showError('source is undefined')
} else if (
(renderType_n === 'DIRECT_URL' ||
renderType_n === 'GOOGLE_READER' ||
renderType_n === 'GOOGLE_DRIVE_VIEWER' ||
renderType_n === 'URL_TO_BASE64') &&
(!source.uri ||
!(
source.uri.startsWith('http') ||
source.uri.startsWith('file') ||
source.uri.startsWith('content')
))
) {
showError(
`source.uri is undefined or not started with http, file or content source.uri = ${source.uri}`,
)
} else if (
(renderType_n === 'BASE64_TO_LOCAL_PDF' ||
renderType_n === 'DIRECT_BASE64') &&
(!source.base64 ||
!source.base64.startsWith('data:application/pdf;base64,'))
) {
showError(
'Base64 is not correct (ie. start with data:application/pdf;base64,)',
)
}
}
async function fetchPdfAsync(source){
const mediaBlob = await urlToBlob(source)
if (mediaBlob) {
return readAsTextAsync(mediaBlob)
}
return undefined
}
async function urlToBlob(source){
if (!source.uri) {
return undefined
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onerror = reject
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
resolve(xhr.response)
}
}
xhr.open('GET', source.uri)
if (source.headers && Object.keys(source.headers).length > 0) {
Object.keys(source.headers).forEach((key) => {
xhr.setRequestHeader(key, source.headers[key])
})
}
xhr.responseType = 'blob'
xhr.send()
})
}
function readAsTextAsync(mediaBlob) {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader()
reader.onloadend = (_e) => {
if (typeof reader.result === 'string') {
return resolve(reader.result)
}
return reject(
`Unable to get result of file due to bad type, waiting string and getting ${typeof reader.result}.`,
)
}
reader.readAsDataURL(mediaBlob)
} catch (error) {
reject(error)
}
})
}
async function writeWebViewReaderFileAsync(data,customStyle,withScroll,withPinchZoom,maximumPinchZoomScale) {
const { exists, md5 } = await getInfoAsync(bundleJsPath, { md5: true })
const bundleContainer = require('./bundleContainer')
if (__DEV__ || !exists || bundleContainer.getBundleMd5() !== md5) {
await writeAsStringAsync(bundleJsPath, bundleContainer.getBundle())
}
await writeAsStringAsync(
htmlPath,
viewerHtml(
data,
customStyle,
withScroll,
withPinchZoom,
maximumPinchZoomScale,
),
)
}
async function writePDFAsync(base64) {
await writeAsStringAsync(
pdfPath,
base64.replace('data:application/pdf;base64,', ''),
{ encoding: EncodingType.Base64 },
)
}
const getGoogleReaderUrl = (url) =>
`https://docs.google.com/viewer?url=${url}`
const getGoogleDriveUrl = (url) =>
`https://drive.google.com/viewerng/viewer?embedded=true&url=${url}`
const init = async () => {
let renderType_n = renderType == undefined ? getRenderType() : renderType;
try {
const {
source,
customStyle,
withScroll,
withPinchZoom,
maximumPinchZoomScale,
} = props
switch (renderType_n) {
case 'GOOGLE_DRIVE_VIEWER': {
break;
}
case 'URL_TO_BASE64': {
const data = await fetchPdfAsync(source)
await writeWebViewReaderFileAsync(
data,
customStyle,
withScroll,
withPinchZoom,
maximumPinchZoomScale,
)
break
}
case 'DIRECT_BASE64': {
await writeWebViewReaderFileAsync(
source.base64,
customStyle,
withScroll,
withPinchZoom,
maximumPinchZoomScale,
)
break
}
case 'BASE64_TO_LOCAL_PDF': {
await writePDFAsync(source.base64)
break
}
default:
break
}
setReady(true);
} catch (error) {
alert(`Sorry, an error occurred. ${error.message}`)
console.error(error)
}
}
async function removeFilesAsync() {
const { exists: htmlPathExist } = await getInfoAsync(htmlPath)
if (htmlPathExist) {
await deleteAsync(htmlPath, { idempotent: true })
}
const { exists: pdfPathExist } = await getInfoAsync(pdfPath)
if (pdfPathExist) {
await deleteAsync(pdfPath, { idempotent: true })
}
}
const originWhitelist = [
'http://*',
'https://*',
'file://*',
'data:*',
'content:*',
]
const style = [styles.pdfview, props.webviewStyle];
const getWebviewSource = () => {
const { source: { uri, headers } } = props;
let renderType_n = renderType == undefined ? getRenderType() : renderType;
switch (renderType_n) {
case 'GOOGLE_READER':
return { uri: getGoogleReaderUrl(uri) }
case 'GOOGLE_DRIVE_VIEWER':
return { uri: getGoogleDriveUrl(uri) };
case 'DIRECT_BASE64':
case 'URL_TO_BASE64':
return { uri: htmlPath }
case 'DIRECT_URL':
return { uri: uri, headers }
case 'BASE64_TO_LOCAL_PDF':
return { uri: pdfPath }
default: {
showError('Unknown RenderType')
return undefined
}
}
}
useEffect(()=>{
setRenderType(getRenderType());
validate();
init();
return () => {
// componentWillUnmount
if (renderType === 'DIRECT_BASE64' || renderType === 'URL_TO_BASE64' || renderType === 'BASE64_TO_LOCAL_PDF'
) {
try {
removeFilesAsync()
} catch (error) {
alert(`Error on removing file. ${error.message}`)
console.error(error)
}
}
}
},[]);
const source = ready ? getWebviewSource() : undefined;
return (
<View style={styles.constainer}>
{ready && source != null ?
<WebView
{...{
originWhitelist,
style,
}}
source={source}
// source = {{uri:undefined}}
onLoad = {()=>{
setReady(true);
}}
onError = {(e)=>{
setReady(false);
Alert.alert('Alert message',e);
}}
allowFileAccess={isAndroid}
allowFileAccessFromFileURLs={isAndroid}
allowUniversalAccessFromFileURLs={isAndroid}
scalesPageToFit={Platform.select({ android: false })}
mixedContentMode={isAndroid ? 'always' : undefined}
sharedCookiesEnabled={false}
startInLoadingState={true}
/>
:
<Loader loading={true} />
}
</View>
);
}
const styles = StyleSheet.create({
constainer:{
flex:1,
backgroundColor:'white',
},
pdfview:{
flex:1,
}
});
const showError = (e) => {
Alert.alert('Alert message', e);
}
function viewerHtml(base64, customStyle, withScroll = false, withPinchZoom = false,maximumPinchZoomScale = 5) {
return `
<!DOCTYPE html>
<html>
<head>
<title>PDF reader</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, maximum-scale=${withPinchZoom ? `${maximumPinchZoomScale}.0` : '1.0'
}, user-scalable=${withPinchZoom ? 'yes' : 'no'}" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/web/pdf_viewer.min.js"></script>
<script
crossorigin
src="https://unpkg.com/react@16/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"
></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.min.js'
</script>
<script type="application/javascript">
try {
window.CUSTOM_STYLE = JSON.parse('${JSON.stringify(
customStyle ?? {},
)}');
} catch (error) {
window.CUSTOM_STYLE = {}
}
try {
window.WITH_SCROLL = JSON.parse('${JSON.stringify(withScroll)}');
} catch (error) {
window.WITH_SCROLL = {}
}
</script>
</head>
<body>
<div id="file" data-file="${base64}"></div>
<div id="react-container"></div>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
`
}
export default PdfReader;
=========== Use ===============
<PdfReader
style={{ width:'100%',height: '100%' }}
source={{
cache:true ,
uri: 'https://www.clickdimensions.com/links/TestPDFfile.pdf',
}}
/>
I hope this will help
Upvotes: 0
Reputation: 399
"rn-pdf-reader-js": "^4.1.1"
"expo": "^40.0.0"
The library work just fine for me in their latest version the only issue comes around that the base64 contain octet-stream so i replace it with pdf like that:
setBase64(reader.result.replace("octet-stream", "pdf"))
and pass it to the source like that:
<PdfReader
source={{
base64: base64,
}}
/>
I hope this helps you. otherwise, please provide us more details about so we can help.
Upvotes: 0
Reputation: 64
rn-pdf-reader-js seems to be not working for the expo 38+. You can try this fork instead:
https://github.com/stratoss/rn-pdf-reader-js
Just import it as import PDFReader from '@bildau/rn-pdf-reader' and you are good to go.
Upvotes: 3