React and Puppeteer: Pdf generation (create pdf-doc view)
Tags: react, pdf, puppeteer, nx
Published: 2022-10-02 13:42:50



Before creating the service to render the pdf, we first need the pdf view.

## Goals
- Ability to render rich text content
- Render SVG components
- Render images
- Render content using layout stying (columns etc.)
- Specifying a custom font

To achieve these goals we installed a few libraries in the previous article.

These were:
- chakra ui
- interweave

# Create the view

### Step 1
First, lets create a RichText component which will be reusable to display richtext content. For the purposes of the experiment, this is the only specialized component we will need. All other useful components will come from `chakra ui`

```tsx
// file: libs/pdf-doc/src/lib/components/rich-text.tsx

import { Box, BoxProps } from '@chakra-ui/react';
import { Markup } from 'interweave';

interface RichTextProps extends BoxProps {
  content: string;
}

export const RichText = ({ content, ...props }: RichTextProps) => {
  return (
    <Box
      {...props}
      sx={{
        p: {
          fontSize: '12px',
          marginBottom: '8px',
        },
        li: {
          fontSize: '12px',
          paddingLeft: '16px',
        },
        ol: {
          paddingInlineStart: '20px',
          mb: '20px',
        },
        ul: {
          paddingInlineStart: '20px',
          mb: '20px',
        },
      }}
    >
      <Markup content={content} />
    </Box>
  );
};
```

### Step 2
Lets create some sample data and types for our template

**_Note:_** The data is truncated. See the repository for full example

```ts
// file: libs/constants/src/lib/constants.ts

export const samplePdfData = {
  images: [
   // this is playful, images will be different per render
'https://source.unsplash.com/category/technology/1600x900',
    'https://source.unsplash.com/category/nature/1600x900',
    'https://source.unsplash.com/category/sports/1600x900',
    'https://source.unsplash.com/category/bike/1600x900',
    'https://source.unsplash.com/category/dogs/1600x900',
    'https://source.unsplash.com/category/cats/1600x900',
  ].map((image, index) => ({ id: index, url: image })),
  title: 'Some random pdf',
  description:
    '<p>Duis autem vel eum iriure dolor in...</p>...',
};
```

```tsx
// file: libs/constants/src/lib/types.ts

import { PDFOptions } from 'puppeteer';

export interface PDFMetadata {
  title: string;
  subject: string;
  author: string;
  producer: string;
  creator: string;
}

export interface Font {
  familyDisplay: string;
  family: string;
  fontFaces?: {
    style?: string;
    weight?: number;
    format?: string;
    src: string;
  }[];
}

export interface PDFData {
  id: string | number;
  metadata: PDFMetadata;
  options?: PDFOptions;
  font: Font;
}

export interface PDFDocumentData<T> extends PDFData {
  document: T;
}
```

**_Note:_** ensure both files are exported by the library

### Step 3
Create a story to help develop the view

```tsx
// file: libs/pdf-doc/src/lib/pdf-doc.stories.tsx

// to run the instance:
// pnpm nx run pdf-doc:storybook 

import { Meta, Story } from '@storybook/react';
import { samplePdfData } from '@pdf-generation/constants';
import { PdfDoc, PdfDocProps } from './pdf-doc';

export default {
  component: PdfDoc,
  title: 'Components/PdfDoc',
} as Meta;

const Template: Story<PdfDocProps> = (args: PdfDocProps) => {
  return <PdfDoc {...args} />;
};

export const DefaultExample = Template.bind({});

DefaultExample.args = samplePdfData;

```

### Step 4

Create your pdf view. This part is totally up to you but see the final pdf component for this experiment.
Also for more details check the repository.

```tsx
import { Box, Heading, Stack, Text } from '@chakra-ui/react';
import { CustomSvg } from './components/custom-svg';
import { Icons } from './components/icons';
import { Images } from './components/images';
import { RichText } from './components/rich-text';
import { SectionTitle } from './components/section-title';
import { ImageData } from './pdf-doc.types';

export interface PdfDocProps {
  images: ImageData[];
  title: string;
  description: string;
}

export function PdfDoc(props: PdfDocProps) {
  const { title, description, images } = props;
  return (
    <Stack spacing={6} fontSize="md">
      <Heading as="h1" size="4xl" color="blue.800">
        {title}
      </Heading>
      <Stack spacing={3}>
        <Text>This is normal text</Text>

        <Heading as="h3" size="l" color="blue.200">
          Description (rich text)
        </Heading>
        <RichText content={description} />
      </Stack>

      <SectionTitle>Images</SectionTitle>
      <Images items={images} />

      <SectionTitle>Icons</SectionTitle>
      <Icons />

      <SectionTitle>Custom Svg</SectionTitle>
      <Box fontSize="200px">
        <CustomSvg />
      </Box>
    </Stack>
  );
}

```

### Step 5

Create a function to render the pdf view as a string and ensure it is exported in `index.ts`

```tsx
// file: libs/pdf-doc/src/lib/pdf-doc.server.tsx

import { renderToString } from 'react-dom/server';
import {
  ChakraProvider,
  extendTheme,
  theme as chakraTheme,
} from '@chakra-ui/react';
import { Font } from '@pdf-generation/constants';
import { PdfDoc, PdfDocProps } from './pdf-doc';

export const renderPdfDoc = (data: PdfDocProps, font?: Font) => {
  const fontFamilyDisplay =
    font?.familyDisplay || font?.family;

  return renderToString(
    <ChakraProvider
      theme={extendTheme({
        fonts: {
          ...chakraTheme.fonts,
          body: fontFamilyDisplay || chakraTheme.fonts.body,
          heading: fontFamilyDisplay || chakraTheme.fonts.heading,
        },
      })}
    >
      <PdfDoc {...data} />
    </ChakraProvider>
  );
};
```

-------


Thats it!

Now to create the controller for generating the pdf