Skip to content

Instantly share code, notes, and snippets.

@josippapez
Last active May 7, 2023 11:44
Show Gist options
  • Select an option

  • Save josippapez/a982bac8b7c997c483e21d8f6788870b to your computer and use it in GitHub Desktop.

Select an option

Save josippapez/a982bac8b7c997c483e21d8f6788870b to your computer and use it in GitHub Desktop.
React-pdf to HTML components
import {
PropsDefs,
PropsDocument,
PropsG,
PropsImage,
PropsLine,
PropsLinearGradient,
PropsLink,
PropsPage,
PropsPath,
PropsRect,
PropsSVG,
PropsStop,
PropsText,
PropsView,
fontWeight,
} from './Types';
import {
Defs,
Document,
G,
Image,
Line,
LinearGradient,
Link,
Page,
Path,
Rect,
Stop,
Svg,
Text,
View,
} from '@react-pdf/renderer';
import { Style } from '@react-pdf/types';
import { FC, useState } from 'react';
let isHtml = true;
export const usePDFComponentsAreHTML = () => {
const [html, setHtml] = useState(isHtml);
isHtml = html;
return {
isHTML: html,
setHtml,
};
};
const fontWeightConverter = (fontWeight?: fontWeight) => {
if (typeof fontWeight === 'number') return fontWeight;
switch (fontWeight) {
case 'thin':
return 100;
case 'hairline':
return 100;
case 'ultralight':
return 200;
case 'extralight':
return 200;
case 'light':
return 300;
case 'normal':
return 400;
case 'medium':
return 500;
case 'semibold':
return 600;
case 'demibold':
return 600;
case 'bold':
return 700;
case 'ultrabold':
return 800;
case 'extrabold':
return 800;
case 'heavy':
return 900;
case 'black':
return 900;
default:
return 400;
}
};
const adjustStyles = (style: Style) => {
if (!style) return;
Object.keys(style).forEach(key => {
if (key === 'paddingVertical') {
style.paddingTop = style[key];
style.paddingBottom = style[key];
} else if (key === 'paddingHorizontal') {
style.paddingLeft = style[key];
style.paddingRight = style[key];
} else if (key === 'fontWeight') {
style.fontWeight = fontWeightConverter(style[key]);
}
return style;
});
};
const mergeStylesIntoOne = (styles: Style[]) => {
const mergedStyle: Style = {};
if (!styles[0]) return mergedStyle;
styles.forEach(style => {
Object.keys(style).forEach(key => {
mergedStyle[key as keyof Style] = style[key as keyof Style];
});
});
return mergedStyle;
};
export const CustomView: FC<PropsView> = ({ children, style, ...rest }) => {
if (isHtml) {
let newStyle = style;
if (Array.isArray(style)) {
newStyle = mergeStylesIntoOne(style) as {
[key: string]: string;
};
}
adjustStyles(newStyle as { [key: string]: string });
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
position: 'relative',
isolation: 'isolate',
left: 0,
right: 0,
...(newStyle as { [key: string]: string }),
}}
>
{children}
</div>
);
}
if (Array.isArray(style)) {
style = [
{
display: 'flex',
flexDirection: 'column',
position: 'relative',
left: 0,
right: 0,
},
...style,
];
} else {
style = {
display: 'flex',
flexDirection: 'column',
position: 'relative',
left: 0,
right: 0,
...style,
};
}
return (
<View style={style} {...rest}>
{children}
</View>
);
};
export const CustomText: FC<PropsText> = ({ children, style, ...rest }) => {
let newStyle = style;
if (Array.isArray(style)) {
newStyle = mergeStylesIntoOne(style) as {
[key: string]: string;
};
}
if (isHtml) {
adjustStyles(newStyle as { [key: string]: string });
return (
<div
style={{
whiteSpace: 'break-spaces',
position: 'relative',
...(newStyle as { [key: string]: string }),
}}
>
{children}
</div>
);
}
return (
<Text
style={{
verticalAlign: 'sub',
...newStyle,
}}
{...rest}
>
{children}
</Text>
);
};
export const CustomImage: FC<PropsImage> = ({ style, ...rest }) => {
if (isHtml) {
let newStyle = style;
if (Array.isArray(style)) {
newStyle = mergeStylesIntoOne(style) as {
[key: string]: string;
};
}
adjustStyles(newStyle as { [key: string]: string });
return (
<img
style={newStyle as { [key: string]: string }}
src={rest.src as string}
/>
);
}
return <Image style={style} {...rest} />;
};
export const CustomPage: FC<PropsPage> = ({ style, children, ...rest }) => {
if (isHtml) {
let newStyle = style;
if (Array.isArray(style)) {
newStyle = mergeStylesIntoOne(style) as {
[key: string]: string;
};
}
adjustStyles(newStyle as { [key: string]: string });
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
position: 'relative',
isolation: 'isolate',
height: '100%',
lineHeight: 'initial',
...(newStyle as { [key: string]: string }),
}}
>
{children}
</div>
);
}
return (
<Page style={style} {...rest}>
{children}
</Page>
);
};
export const CustomLink: FC<PropsLink> = ({ children, style, ...rest }) => {
if (isHtml) {
let newStyle = style;
if (Array.isArray(style)) {
newStyle = mergeStylesIntoOne(style) as {
[key: string]: string;
};
}
adjustStyles(newStyle as { [key: string]: string });
return (
<a {...rest} href={rest.src} target='_blank' rel='noopener noreferrer'>
<div style={newStyle as { [key: string]: string }}>{children}</div>
</a>
);
}
return (
<Link {...rest} style={style}>
{children}
</Link>
);
};
export const CustomG: FC<PropsG> = ({ children, ...rest }) => {
if (isHtml) {
return <g {...rest}>{children}</g>;
}
return <G {...rest}>{children}</G>;
};
export const CustomPath: FC<PropsPath> = ({ children, ...rest }) => {
if (isHtml) {
return <path {...rest}>{children}</path>;
}
return <Path {...rest}>{children}</Path>;
};
export const CustomRect: FC<PropsRect> = ({ children, ...rest }) => {
if (isHtml) {
return <rect {...rest}>{children}</rect>;
}
return <Rect {...rest}>{children}</Rect>;
};
export const CustomSVG: FC<PropsSVG> = ({ children, ...rest }) => {
if (isHtml) {
const style = {
left: 0,
right: 0,
...rest.style,
};
return (
<svg
{...rest}
style={{
...(style as { [key: string]: string | number }),
}}
>
{children}
</svg>
);
}
return <Svg {...rest}>{children}</Svg>;
};
export const CustomDefs: FC<PropsDefs> = ({ children, ...rest }) => {
if (isHtml) {
return <defs {...rest}>{children}</defs>;
}
return <Defs {...rest}>{children}</Defs>;
};
export const CustomLine: FC<PropsLine> = ({ children, ...rest }) => {
if (isHtml) {
return <line {...rest}>{children}</line>;
}
return <Line {...rest}>{children}</Line>;
};
export const CustomStop: FC<PropsStop> = ({ children, ...rest }) => {
if (isHtml) {
return <stop {...rest}>{children}</stop>;
}
return <Stop {...rest}>{children}</Stop>;
};
export const CustomLinearGradient: FC<PropsLinearGradient> = ({
children,
...rest
}) => {
if (isHtml) {
return <linearGradient {...rest}>{children}</linearGradient>;
}
return <LinearGradient {...rest}>{children}</LinearGradient>;
};
export const CustomDocument: FC<PropsDocument> = ({ children, ...rest }) => {
if (isHtml) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
position: 'relative',
isolation: 'isolate',
left: 0,
right: 0,
minHeight: '100%',
}}
>
{children}
</div>
);
}
return <Document {...rest}>{children}</Document>;
};
export {
CustomDefs as Defs,
CustomDocument as Document,
CustomG as G,
CustomImage as Image,
CustomLine as Line,
CustomLinearGradient as LinearGradient,
CustomLink as Link,
CustomPage as Page,
CustomPath as Path,
CustomRect as Rect,
CustomStop as Stop,
CustomSVG as Svg,
CustomText as Text,
CustomView as View,
};
import {
PageProps,
SVGPresentationAttributes,
SourceObject,
Style,
TextProps,
ViewProps,
DocumentProps,
} from '@react-pdf/types';
import { PropsWithChildren } from 'react';
export interface PropsView extends PropsWithChildren<ViewProps> {
style?: Style | Style[];
}
export interface PropsText extends PropsWithChildren<TextProps> {
style?: Style | Style[];
}
export interface PropsImage extends ImageWithSrcProp {
style?: Style | Style[];
}
export interface PropsPage extends PropsWithChildren<PageProps> {
style?: Style | Style[];
}
export interface PropsLink extends PropsWithChildren<LinkProps> {
style?: Style | Style[];
}
export interface PropsSVG extends PropsWithChildren<SVGProps> {
style?: Style | Style[];
}
export type PropsRect = PropsWithChildren<RectProps>;
export type PropsPath = PropsWithChildren<PathProps>;
export type PropsG = PropsWithChildren<SVGPresentationAttributes>;
export type PropsLine = PropsWithChildren<LineProps>;
export type PropsStop = PropsWithChildren<StopProps>;
export type PropsLinearGradient = PropsWithChildren<LinearGradientProps>;
export type PropsDefs = PropsWithChildren;
export type PropsDocument = PropsWithChildren<DocumentProps>;
export type { Style as StylePDF };
// react-pdf/ types
interface NodeProps {
id?: string;
style?: Style | Style[];
/**
* Render component in all wrapped pages.
* @see https://react-pdf.org/advanced#fixed-components
*/
fixed?: boolean;
/**
* Force the wrapping algorithm to start a new page when rendering the
* element.
* @see https://react-pdf.org/advanced#page-breaks
*/
break?: boolean;
/**
* Hint that no page wrapping should occur between all sibling elements following the element within n points
* @see https://react-pdf.org/advanced#orphan-&-widow-protection
*/
minPresenceAhead?: number;
}
interface BaseImageProps extends NodeProps {
/**
* Enables debug mode on page bounding box.
* @see https://react-pdf.org/advanced#debugging
*/
debug?: boolean;
cache?: boolean;
}
interface ImageWithSrcProp extends BaseImageProps {
src: SourceObject;
}
interface ImageWithSourceProp extends BaseImageProps {
source: SourceObject;
}
interface LinkProps extends NodeProps {
/**
* Enable/disable page wrapping for element.
* @see https://react-pdf.org/components#page-wrapping
*/
wrap?: boolean;
/**
* Enables debug mode on page bounding box.
* @see https://react-pdf.org/advanced#debugging
*/
debug?: boolean;
src: string;
}
interface SVGProps extends NodeProps {
/**
* Enables debug mode on page bounding box.
* @see https://react-pdf.org/advanced#debugging
*/
debug?: boolean;
width?: string | number;
height?: string | number;
viewBox?: string;
preserveAspectRatio?: string;
}
interface RectProps extends SVGPresentationAttributes {
style?: SVGPresentationAttributes;
x: string | number;
y: string | number;
width: string | number;
height: string | number;
rx?: string | number;
ry?: string | number;
}
interface PathProps extends SVGPresentationAttributes {
style?: SVGPresentationAttributes;
d: string;
}
interface LineProps extends SVGPresentationAttributes {
style?: SVGPresentationAttributes;
x1: string | number;
x2: string | number;
y1: string | number;
y2: string | number;
}
interface StopProps {
offset: string | number;
stopColor: string;
stopOpacity?: string | number;
}
interface LinearGradientProps {
id: string;
x1: string | number;
x2: string | number;
y1: string | number;
y2: string | number;
}
// interface DefsProps {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment