How to generate a PDF document in a React Native project?
Tags: reactnative, mobile, ios, android
Published: 2021-02-18 13:02:53

Originally, this post was published on [UppLabs blog](https://upplabs.com/how-to-generate-a-pdf-document-in-a-react-native-project/) by Pavlo Ihnatiev.

<p>Not so long ago UppLabs was working on a <a href="https://upplabs.com/upplabs-created-healthcare-app-for-preventing-hair-loss/" target="_blank" rel="noreferrer noopener">project</a> 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.&nbsp;</p>

<h3>Basics</h3>

<p>The simplest way to create a PDF document in a React Native project includes the use of <a href="https://docs.expo.io/versions/latest/sdk/print" target="_blank" rel="noreferrer noopener">Expo Print</a> plugin.</p>

<p>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.</p>

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

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

<!-- wp:paragraph -->
<p>Then you should write function, that creates a PDF document with defined HTML markup:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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);
    }
};</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p><strong>Notice</strong>:&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>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 <a href="https://docs.expo.io/versions/latest/sdk/filesystem" target="_blank" rel="noreferrer noopener">Expo Filesystem</a>. If you want to save it to the Gallery on your device, you can use&nbsp; <a href="https://docs.expo.io/versions/latest/sdk/media-library" target="_blank" rel="noreferrer noopener">Expo MediaLibrary</a> on Android platform and <a href="https://docs.expo.io/versions/latest/sdk/sharing" target="_blank" rel="noreferrer noopener">Expo Sharing</a> on IOS platform.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Example of function, that allows you to create PDF and save it to Gallery:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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);
  }
};</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2><strong>Advanced cases</strong> </h2>
<!-- /wp:heading -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":3} -->
<h3><strong>PDF page margin</strong></h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If you're using the function <strong>Print.printToFileAsync</strong>, the resulting PDF document might contain page margins (it depends on WebView engine). They are set by <strong>@page</strong> style block and you can override them in your HTML content&nbsp; code:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;style> 
   @page { margin: 20px; } 
&lt;/style></code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3><strong>Avoiding breaking content</strong></h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To control the breaking of some HTML elements you can use the <a href="https://css-tricks.com/almanac/properties/b/break-inside/" target="_blank" rel="noreferrer noopener">“<strong>break-inside</strong>”</a> CSS property in your HTML slicing.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>section {
  break-inside: avoid;
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3><strong>Using fonts, images and other assets in PDF</strong></h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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,&nbsp; on the IOS platform, local files can’t be loaded to PDF as well, so you should convert the local file to <strong>base64</strong> string. After that, you can use the URL of the copied file (on Android platform) or <strong>base64 </strong>string (on IOS Platform) in your HTML markup.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In such cases, you can use <a href="https://docs.expo.io/versions/latest/sdk/asset/" target="_blank" rel="noreferrer noopener">Expo Asset</a> and <a href="https://docs.expo.io/versions/latest/sdk/imagemanipulator" target="_blank" rel="noreferrer noopener">Expo ImageManipulator</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Example of code, that copies an image from assets and converts it to <strong>base64 </strong>for IOS platform before using it in a PDF document:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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&#91;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,
      &#91;],
      { 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 `&lt;img src="${src}" alt="Logo" />`
    } catch (error) {
        console.log(error);
    }
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>So after calling this code you can use the returned URL of the asset image in your PDF document.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3><strong>Optimization of large images</strong></h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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 <a href="https://docs.expo.io/versions/latest/sdk/imagemanipulator" target="_blank" rel="noreferrer noopener">Expo ImageManipulator</a>.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>Notice</strong>: This plugin works only with images placed locally on the device. You need to download the image first.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Example of function, that optimizes large images:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import * as ImageManipulator from 'expo-image-manipulator';


....

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

      return uri;
  } catch (error) {
      console.log(error);
      return imageUri;
  }
};</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>Check the example! </h3>
<!-- /wp:heading -->

<!-- wp:image {"align":"center","id":875,"width":256,"height":256,"sizeSlug":"large","className":"is-style-default"} -->
<div class="wp-block-image is-style-default"><figure class="aligncenter size-large is-resized"><a href="https://expo.io/@upplabs/upplabs-pdf-example" target="_blank" rel="noopener noreferrer"><img src="https://upplabs.com/wp-content/uploads/2020/06/expo-logo-1.png" alt="See the exmple of how UppLabs' app for creating PDF on mobile works" class="wp-image-875" width="256" height="256"/></a><figcaption><a href="https://expo.io/@upplabs/upplabs-pdf-example" target="_blank" rel="noreferrer noopener">https://expo.io/@upplabs/upplabs-pdf-example</a></figcaption></figure></div>
<!-- /wp:image -->

<!-- wp:image {"align":"center","id":876,"width":250,"height":250,"sizeSlug":"large"} -->
<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><a href="https://snack.expo.io/@upplabs/upplabs-pdf-" target="_blank" rel="noopener noreferrer"><img src="https://upplabs.com/wp-content/uploads/2020/06/snack-expo-upplabs-1.png" alt="See the example of how UppLabs' app for creating PDF on mobile works" class="wp-image-876" width="250" height="250"/></a><figcaption><a rel="noreferrer noopener" href="https://snack.expo.io/@upplabs/upplabs-pdf-" target="_blank">https://snack.expo.io/@upplabs/upplabs-pdf-</a></figcaption></figure></div>
<!-- /wp:image -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:spacer {"height":50} -->
<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<div class="wp-block-button"><a class="wp-block-button__link" href="https://upplabs.com/portfolio/hairmax/" target="_blank" rel="noreferrer noopener">View the case study</a></div>

Thank you for reading!