React Native PDF Digital Signature, for PDF documents.

Alameda Dev.
Feb 24, 2020 · 9 min read

In this post we are going to explore, making a component for signing digitally a PDF document. We decide to explore different way to do it.

Last updated May 29, 2020

Image for post
Image for post

In this post we are going to explore, making a component for signing digitally a PDF document.

I will explain one approach, and one way we can do this, take into account that this example is far from perfect, but it can help you to get an idea of how something like this can be accomplished;

Find the complete code here https://github.com/uokesita/RNPdfSignature

Please do not use this code directly as it is not performant nor the best solution for the bests results. This was an experiment to see how far can we get with current libraries for React Native because there are no free alternatives.

So let’s start.

Image for post
Image for post

Project setup

And install our fist dependency for Loading and viewing a PDF document on the screen, we are going to use Wonday react-native-pdf: https://github.com/wonday/react-native-pdf

npm install react-native-pdf rn-fetch-blob

Don’t forget to install the packages via pods
cd ios; pod install; cd ..

We now have our basic React Native application.

Image for post
Image for post

Modify the Main Screen to show a PDF as per Wonday example:

import React from 'react';
import { StyleSheet, Dimensions, View } from 'react-native';
import Pdf from 'react-native-pdf';
export default PDFExample = () => {
const source = {uri:'http://samples.leanpub.com/thereactnativebook-sample.pdf',cache:true};
return (
<View style={styles.container}>
<Pdf
source={source}
onLoadComplete={(numberOfPages,filePath, {width, height})=>{
console.log(`number of pages: ${numberOfPages}`);
console.log(`width: ${width}`);
console.log(`height: ${height}`);
}}
onPageChanged={(page,numberOfPages)=>{
console.log(`current page: ${page}`);
}}
onError={(error)=>{
console.log(error);
}}
onPressLink={(uri)=>{
console.log(`Link presse: ${uri}`)
}}
style={styles.pdf}/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginTop: 25,
backgroundColor: '#f4f4f4'
},
pdf: {
width:Dimensions.get('window').width,
height: Dimensions.get('window'). height,
}
});
Image for post
Image for post

Making the signature to work will be easier if we show only one page on the screen, so modify the code for the pdf component like this:

<Pdf
minScale={1.0}
maxScale={1.0}
scale={1.0}
spacing={0}
fitPolicy={0}
enablePaging={true}

/>
Image for post
Image for post

It will help us also, to show the pdf on a fixed height container

container: {
flex:1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 25,
backgroundColor: '#f4f4f4'
},
pdf: {
width:Dimensions.get('window').width,
height: 540,
}
Image for post
Image for post

Support for onPageSingleTap

Once we add the function handler to App.js:

// App.js
<Pdf

onPageSingleTap={(page, x, y) => {
console.log(`tap: ${page}`);
console.log(`x: ${x}`);
console.log(`y: ${y}`);
}}

/>

We should see something like this on the console:

Image for post
Image for post

Store PDF on the device

npm install react-native-fs --save
react-native link react-native-fs
cd ios; pod install; cd ..

Let’s store the document on the device on the app launch.

import React, { useEffect, useState } from 'react';
import { StyleSheet, Dimensions, View, Text } from 'react-native';
import Pdf from 'react-native-pdf';
const RNFS = require('react-native-fs');
export default PDFExample = () => {
const sourceUrl = 'http://samples.leanpub.com/thereactnativebook-sample.pdf';
const filePath = '${RNFS.DocumentDirectoryPath}/react-native.pdf';
const [fileDownloaded, setFileDownloaded] = useState(false);
useEffect(() => {
this.downloadFile()
}, []);
downloadFile = () => {
console.log("___downloadFile -> Start");
RNFS.downloadFile({
fromUrl: sourceUrl,
toFile: filePath,
}).promise.then((res) => {
console.log("___downloadFile -> File downloaded", res);
setFileDownloaded(true);
})
}
return (
<View style={styles.container}>
{ fileDownloaded && (
<Pdf
minScale={1.0}
maxScale={1.0}
scale={1.0}
spacing={0}
fitPolicy={0}
enablePaging={true}
source={{uri: filePath}}
usePDFKit={false}
onLoadComplete={(numberOfPages,filePath, {width, height})=>{
console.log(`number of pages: ${numberOfPages}`);
console.log(`width: ${width}`);
console.log(`height: ${height}`);
}}
onPageSingleTap={(page, x, y) => {
console.log(`tap: ${page}`);
console.log(`x: ${x}`);
console.log(`y: ${y}`);
}}
onPageChanged={(page,numberOfPages)=>{
console.log(`current page: ${page}`);
}}
onError={(error)=>{
console.log(error);
}}
onPressLink={(uri)=>{
console.log(`Link presse: ${uri}`)
}}
style={styles.pdf}/>
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 25,
backgroundColor: '#f4f4f4'
},
pdf: {
width: Dimensions.get('window').width,
height: 540,
}
});

Get signature from the user

npm install react-native-signature-canvas --save

Be careful with this issue: Invariant Violation: requireNativeComponent: “RNCWebView” was not found in the UIManager · Issue #18 · YanYuanFE/react-native-signature-canvas · GitHub

import React, { useEffect, useState } from 'react';
import { StyleSheet, Dimensions, View, Text, Button } from 'react-native';
import Pdf from 'react-native-pdf';
const RNFS = require('react-native-fs');
import Signature from 'react-native-signature-canvas';
export default PDFExample = () => {
const sourceUrl = 'http://samples.leanpub.com/thereactnativebook-sample.pdf';
const filePath = `${RNFS.DocumentDirectoryPath}/react-native.pdf`;
const [fileDownloaded, setFileDownloaded] = useState(false);
const [getSignaturePad, setSignaturePad] = useState(false);
const [signatureBase64, setsignatureBase64] = useState("");
useEffect(() => {
this.downloadFile()
}, []);
downloadFile = () => {
console.log("___downloadFile -> Start");
RNFS.downloadFile({
fromUrl: sourceUrl,
toFile: filePath,
}).promise.then((res) => {
console.log("___downloadFile -> File downloaded", res);
setFileDownloaded(true);
})
}
getSignature = () => {
console.log("___getSignature -> Start");
setSignaturePad(true);
}
handleSignature = signature => {
console.log("___handleSignature -> Start", signature);
setsignatureBase64(signature);
setSignaturePad(false);
}
return (
<View style={styles.container}>
{ getSignaturePad ? (
<Signature
onOK={(sig) => this.handleSignature(sig)}
onEmpty={() => console.log('___onEmpty')}
descriptionText="Sign"
clearText="Clear"
confirmText="Save"
/>
) : ((fileDownloaded) && (
<View>
<Button
title="Sign Document"
onPress={this.getSignature}
/>
<Pdf
minScale={1.0}
maxScale={1.0}
scale={1.0}
spacing={0}
fitPolicy={0}
enablePaging={true}
source={{uri: filePath}}
usePDFKit={false}
onLoadComplete={(numberOfPages,filePath, {width, height})=>{
console.log(`number of pages: ${numberOfPages}`);
console.log(`width: ${width}`);
console.log(`height: ${height}`);
}}
onPageSingleTap={(page, x, y) => {
console.log(`tap: ${page}`);
console.log(`x: ${x}`);
console.log(`y: ${y}`);
}}
onPageChanged={(page,numberOfPages)=>{
console.log(`current page: ${page}`);
}}
onError={(error)=>{
console.log(error);
}}
onPressLink={(uri)=>{
console.log(`Link presse: ${uri}`)
}}
style={styles.pdf}/>
</View>
))}

</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 25,
backgroundColor: '#f4f4f4'
},
pdf: {
width: Dimensions.get('window').width,
height: 540,
}
});

After we have the signature stored as Base64 string. We will aso need to convert all Base64 files into ArrayBuffer. Then, we can start manipulating the pdf file.

Place signature on PDF

  • We need a way to put the pdf view in “edit mode”.
  • Ask the user for the place of the signature.
  • Save the touched coordinates.
  • Add the signature to the pdf on the coordinates.
  • And save the pdf document.

For all this, we need now a package for manipulating the pdf file, edit it and add images. The package we are going to use is GitHub — Hopding/pdf-lib: Create and modify PDF documents in any JavaScript environment

npm install pdf-lib base-64 --save

The final code will look like this now:

/**
* Copyright (c) 2020-present, Bouncing Shield (bouncingshield.com)
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useEffect, useState } from "react";
import { StyleSheet, Dimensions, View, Text, Image, TouchableOpacity, Platform } from "react-native";
import Pdf from "react-native-pdf";
const RNFS = require("react-native-fs");
import { PDFDocument } from "pdf-lib";
import Signature from "react-native-signature-canvas";
import { decode as atob, encode as btoa } from "base-64"
export default PDFExample = () => {
const sourceUrl = "http://samples.leanpub.com/thereactnativebook-sample.pdf";
const [fileDownloaded, setFileDownloaded] = useState(false);
const [getSignaturePad, setSignaturePad] = useState(false);
const [pdfEditMode, setPdfEditMode] = useState(false);
const [signatureBase64, setSignatureBase64] = useState(null);
const [signatureArrayBuffer, setSignatureArrayBuffer] = useState(null);
const [pdfBase64, setPdfBase64] = useState(null);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState(null);
const [newPdfSaved, setNewPdfSaved] = useState(false);
const [newPdfPath, setNewPdfPath] = useState(null);
const [pageWidth, setPageWidth] = useState(0);
const [pageHeight, setPageHeight] = useState(0);
const [filePath, setFilePath] = useState(`${RNFS.DocumentDirectoryPath}/react-native.pdf`);
useEffect(() => {
this.downloadFile();
if (signatureBase64){
setSignatureArrayBuffer(this._base64ToArrayBuffer(signatureBase64));
}
if (newPdfSaved){
setFilePath(newPdfPath);
setPdfArrayBuffer(this._base64ToArrayBuffer(pdfBase64));
}
console.log('filePath', filePath)
}, [signatureBase64, filePath, newPdfSaved]);
_base64ToArrayBuffer = (base64) => {
const binary_string = atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
_uint8ToBase64 = (u8Arr) => {
const CHUNK_SIZE = 0x8000; //arbitrary number
let index = 0;
const length = u8Arr.length;
let result = "";
let slice;
while (index < length) {
slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
result += String.fromCharCode.apply(null, slice);
index += CHUNK_SIZE;
}
return btoa(result);
}
downloadFile = () => {
if (!fileDownloaded){
RNFS.downloadFile({
fromUrl: sourceUrl,
toFile: filePath,
}).promise.then((res) => {
setFileDownloaded(true);
this.readFile();
});
}
}
readFile = () => {
RNFS.readFile(`${RNFS.DocumentDirectoryPath}/react-native.pdf`, "base64").then((contents) => {
setPdfBase64(contents);
setPdfArrayBuffer(this._base64ToArrayBuffer(contents));
})
}
getSignature = () => {
setSignaturePad(true);
}
handleSignature = signature => {
setSignatureBase64(signature.replace("data:image/png;base64,", ""));
setSignaturePad(false);
setPdfEditMode(true);
}
handleSingleTap = async (page, x, y) => {
if (pdfEditMode){
setNewPdfSaved(false);
setFilePath(null);
setPdfEditMode(false);
const pdfDoc = await PDFDocument.load(pdfArrayBuffer);
const pages = pdfDoc.getPages();
const firstPage = pages[page - 1]
// The meat
const signatureImage = await pdfDoc.embedPng(signatureArrayBuffer)
if (Platform.OS == 'ios') {
firstPage.drawImage(signatureImage, {
x: ((pageWidth * (x - 12)) / Dimensions.get("window").width),
y: pageHeight - ((pageHeight * (y + 12)) / 540),
width: 50,
height: 50,
})
} else {
firstPage.drawImage(signatureImage, {
x: (firstPage.getWidth() * x ) / pageWidth,
y: (firstPage.getHeight() - ((firstPage.getHeight() * y ) / pageHeight)) - 25,
width: 50,
height: 50,
})
}
// Play with these values as every project has different requirements
const pdfBytes = await pdfDoc.save();
const pdfBase64 = this._uint8ToBase64(pdfBytes);
const path = `${RNFS.DocumentDirectoryPath}/react-native_signed_${Date.now()}.pdf`;
console.log('path', path)
RNFS.writeFile(path, pdfBase64, "base64").then((success) => {
setNewPdfPath(path);
setNewPdfSaved(true);
setPdfBase64(pdfBase64);
})
.catch((err) => {
console.log(err.message);
});
}
}
return (
<View style={styles.container}>
{ getSignaturePad ? (
<Signature
onOK={(sig) => this.handleSignature(sig)}
onEmpty={() => console.log("___onEmpty")}
descriptionText="Sign"
clearText="Clear"
confirmText="Save"
/>
) : ((fileDownloaded) && (
<View>
{ filePath ? (
<View>
<Text style={styles.headerText}>React Native Digital PDF Signature</Text>
<Pdf
minScale={1.0}
maxScale={1.0}
scale={1.0}
spacing={0}
fitPolicy={0}
enablePaging={true}
source={{uri: filePath}}
usePDFKit={false}
onLoadComplete={(numberOfPages, filePath, {width, height})=>{
setPageWidth(width);
setPageHeight(height);
}}
onPageSingleTap={(page, x, y) => {
this.handleSingleTap(page, x, y);
}}
style={styles.pdf}/>
</View>
) : (
<View style={styles.button}>
<Text style={styles.buttonText}>Saving PDF File...</Text>
</View>
)}
{ pdfEditMode ? (
<View style={styles.message}>
<Text>* EDIT MODE *</Text>
<Text>Touch where you want to place the signature</Text>
</View>
) : (filePath && (
<View>
<TouchableOpacity
onPress={this.getSignature}
style={styles.button}
>
<Text style={styles.buttonText}>Sign Document</Text>
</TouchableOpacity>
<View>
<Image
source={{uri: "http://www.bouncingshield.com/icons/icon-512x512.png"}}
style={{width: 40, height: 40, alignSelf: "center"}}
/>
<Text style={styles.headerText}>bouncingshield.com</Text>
</View>
</View>
))}
</View>
))}

</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f4f4f4"
},
headerText: {
color: "#508DBC",
fontSize: 20,
marginBottom: 20,
alignSelf: "center"
},
pdf: {
width: Dimensions.get("window").width,
height: 540,
},
button: {
alignItems: "center",
backgroundColor: "#508DBC",
padding: 10,
marginVertical: 10
},
buttonText: {
color: "#DAFFFF",
},
message: {
alignItems: "center",
padding: 15,
backgroundColor: "#FFF88C"
}
});

Now we have something like this:

Image for post
Image for post

Applications

  • Load the pdf from the user device documents.
  • Save signature on the device and use it as many times as required.
  • Save many signatures and choose which one to use.
  • Change signature size and color.
  • Share the signed pdf.
  • Add another kind of images as stamps or geometrical forms.
  • Etc.

Please do not use this code directly as it is not performant nor the best solution for the bests results. This was an experiment to see how far can we get with current libraries for React Native, because there are no free alternatives.

Find the complete code here
https://github.com/uokesita/RNPdfSignature

Bouncing Shield is part of Alameda dev Team. If you have any questions, you can contact us at hola@alamedadev.com.
And https://alamedadev.com

Alameda Dev.

Specialized in Design and Development of Mobile Apps, Web with React Ecosystem

Alameda Dev.

Written by

¡Hola! We are a team 100% Remote. Specialized Development of Mobile Apps, Web and advanced software with React and ReactNative. www.alamedadev.com

Alameda Dev.

We are a small agency 100% Remote. Specialized in Design and Development of Mobile Apps, Web and advanced software with React Ecosystem.

Alameda Dev.

Written by

¡Hola! We are a team 100% Remote. Specialized Development of Mobile Apps, Web and advanced software with React and ReactNative. www.alamedadev.com

Alameda Dev.

We are a small agency 100% Remote. Specialized in Design and Development of Mobile Apps, Web and advanced software with React Ecosystem.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store