Last active
May 7, 2023 11:44
-
-
Save josippapez/a982bac8b7c997c483e21d8f6788870b to your computer and use it in GitHub Desktop.
React-pdf to HTML components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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