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>
);
};
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>...',
};
// 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
// 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.
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
// 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
Top comments (0)