DEV Community

Layton Whiteley
Layton Whiteley

Posted on

React and Puppeteer: Pdf generation (create pdf-doc view)

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

// 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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Step 2

Lets create some sample data and types for our template

Note: The data is truncated. See the repository for full example

// 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>...',
};
Enter fullscreen mode Exit fullscreen mode
// 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;
}
Enter fullscreen mode Exit fullscreen mode

Note: ensure both files are exported by the library

Step 3

Create a story to help develop the view

// 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;

Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

Step 5

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

// 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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Thats it!

Now to create the controller for generating the pdf

Top comments (0)