DEV Community

Cover image for How to generate a PDF document in a React Native project?
UppLabs
UppLabs

Posted on

How to generate a PDF document in a React Native project?

Originally, this post was published on UppLabs blog by Pavlo Ihnatiev.

Not so long ago UppLabs was working on a project which key functionalities included a PDF generation on mobile. In this material, we’ll share some tips on how to generate a PDF document using React Native. 

Basics

The simplest way to create a PDF document in a React Native project includes the use of Expo Print plugin.

The first step is to write HTML with a sample of content that should be in our PDF. You can insert CSS styles, custom fonts, images, links, etc to your markup.

const htmlContent = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Pdf Content</title>
        <style>
            body {
                font-size: 16px;
                color: rgb(255, 196, 0);
            }

            h1 {
                text-align: center;
            }
        </style>
    </head>
    <body>
        <h1>Hello, UppLabs!</h1>
    </body>
    </html>
`;

Then you should write function, that creates a PDF document with defined HTML markup:

import * as Print from 'expo-print';

const htmlContent = `
....
`;

const createPDF = async (html) => {
    try {
        const { uri } = await Print.printToFileAsync({ html });
        return uri;
    } catch (err) {
        console.error(err);
    }
};

Notice

This function will create a PDF file and save it to the application cache folder. So if you want to replace or copy this file, you can use Expo Filesystem. If you want to save it to the Gallery on your device, you can use  Expo MediaLibrary on Android platform and Expo Sharing on IOS platform.

Example of function, that allows you to create PDF and save it to Gallery:

import * as Print from "expo-print";
import * as MediaLibrary from "expo-media-library";
import * as Sharing from "expo-sharing";


....

const createAndSavePDF = async (html) => {
  try {
    const { uri } = await Print.printToFileAsync({ html });
    if (Platform.OS === "ios") {
      await Sharing.shareAsync(uri);
    } else {
      const permission = await MediaLibrary.requestPermissionsAsync();

      if (permission.granted) {
        await MediaLibrary.createAssetAsync(uri);
      }
    }

  } catch (error) {
    console.error(error);
  }
};

You can use this functionality in your React Native components and that will be enough if you need to create a quick PDF document with simple content.

Advanced cases


PDF page margin

If you're using the function Print.printToFileAsync, the resulting PDF document might contain page margins (it depends on WebView engine). They are set by @page style block and you can override them in your HTML content  code:

<style> 
   @page { margin: 20px; } 
</style>

Avoiding breaking content

If you insert a few content sections in your HTML markup, it may not fit together on the same document page. In such cases, the content of the second section breaks, and part of it is placed on the next page of the document. But sometimes such an approach is unacceptable when you want to avoid breaking the content section.

To control the breaking of some HTML elements you can use the break-inside CSS property in your HTML slicing.

section {
  break-inside: avoid;
}

So, if two sections are not fit on the same page, the second section will be placed on the next page of the document without breaking.

Using fonts, images and other assets in PDF

You can use custom fonts, images, or other assets in your HTML without any troubles if it’s placed remotely. But if you want to use the image from React Native app assets, it may be a problem, because such images can’t be loaded to your document when your React Native app is built for production. In such cases, you should first copy the file from assets to your application’s cache directory. Unfortunately,  on the IOS platform, local files can’t be loaded to PDF as well, so you should convert the local file to base64 string. After that, you can use the URL of the copied file (on Android platform) or base64 string (on IOS Platform) in your HTML markup.

In such cases, you can use Expo Asset and Expo ImageManipulator.

Example of code, that copies an image from assets and converts it to base64 for IOS platform before using it in a PDF document:

import { Platform } from "react-native";
import { Asset } from "expo-asset";
import * as ImageManipulator from "expo-image-manipulator";


....

const copyFromAssets = async (asset) => {
  try {
    await Asset.loadAsync(asset);
    const { localUri } = Asset.fromModule(asset);

    return localUri;
  } catch (error) {
    console.log(error);
    throw err;
  }
};


const processLocalImageIOS = async (imageUri) => {
  try {
    const uriParts = imageUri.split(".");
    const formatPart = uriParts[uriParts.length - 1];
    let format;

    if (formatPart.includes("png")) {
      format = "png";
    } else if (formatPart.includes("jpg") || formatPart.includes("jpeg")) {
      format = "jpeg";
    }

    const { base64 } = await ImageManipulator.manipulateAsync(
      imageUri,
      [],
      { format: format || "png", base64: true }
    );

    return `data:image/${format};base64,${base64}`;
  } catch (error) {
    console.log(error);
    throw error
  }
};

....

const htmlContent = async () => {
    try {
        const asset = require('./assets/logo.png');
        let src = await copyFromAssets(asset);
        if(Platform.OS === 'ios') {
            src = await processLocalImageIOS(src);
        }

        return `<img src="${src}" alt="Logo" />`
    } catch (error) {
        console.log(error);
    }
}

So after calling this code you can use the returned URL of the asset image in your PDF document.

Optimization of large images

If you want to place large images in your PDF document, you should optimize them before. It can greatly reduce the size of your PDF document. Optimizing the images is useful when you want to insert images that are made using the camera of your device. To optimize images you can use Expo ImageManipulator

Notice: This plugin works only with images placed locally on the device. You need to download the image first.

Example of function, that optimizes large images:

import * as ImageManipulator from 'expo-image-manipulator';


....

const optimizeImage = async (imageUri) => {
  try {
      const { uri } = await ImageManipulator.manipulateAsync(
        imageUri,
          [
              {
                  resize: {
                      width: 600,
                  },
              },
          ],
          { compress: 0.1 },
      );

      return uri;
  } catch (error) {
      console.log(error);
      return imageUri;
  }
};

Check the example!

See the exmple of how UppLabs' app for creating PDF on mobile works
https://expo.io/@upplabs/upplabs-pdf-example

See the example of how UppLabs' app for creating PDF on mobile works
https://snack.expo.io/@upplabs/upplabs-pdf-


View the case study

Thank you for reading!

Top comments (1)

Collapse
 
claudemirsoftmais profile image
claudemir-softmais

Hello,
How can I insert the page number in pdf, using this example, react-native ?


<br> table, th, td {<br> border: 1px solid black;<br> border-collapse: collapse;<br> }<br> p {margin: 0;}</p> <div class="highlight"><pre class="highlight plaintext"><code>@page { size: A4; margin: 100px; @bottom-center { content: counter(page); } } </code></pre></div> <p>
/head>
<body>
 ...
Enter fullscreen mode Exit fullscreen mode

It doesn't work ...

Thanks..