├── .gitignore ├── .npmignore ├── .DS_Store ├── babel.config.js ├── jsr.json ├── .github └── workflows │ └── npm-publish.yml ├── tsconfig.json ├── package.json ├── README.md └── src ├── Types.ts └── index.tsx /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josippapez/react-pdf-html/HEAD/.DS_Store -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [["@babel/preset-env"]], 3 | }; 4 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rawwee/react-pdf-html", 3 | "version": "0.1.0", 4 | "exports": "./src/index.tsx" 5 | } -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 20 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm ci 20 | - run: npm run build 21 | - run: npm publish 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "outDir": "dist", 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["./src/*"], 21 | "@modules/*": ["./modules/*"] 22 | } 23 | }, 24 | "ts-node": { 25 | "compilerOptions": { 26 | "module": "CommonJS", 27 | "isolatedModules": false 28 | } 29 | }, 30 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 31 | "exclude": ["node_modules", "dist"] 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rawwee/react-pdf-html", 3 | "version": "1.2.1", 4 | "description": "Package for rendering @react-pdf/renderer components as HTML components", 5 | "sideEffects": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "clean": "rm -rf dist", 9 | "build": "npm run clean && npm run build:core", 10 | "build:core": "microbundle src/*.tsx --jsx React.createElement --jsxFragment React.Fragment" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/josippapez/react-pdf-html.git" 15 | }, 16 | "keywords": [ 17 | "React", 18 | "React-PDF", 19 | "PDF" 20 | ], 21 | "author": "Josip Papež", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/josippapez/react-pdf-html/issues" 25 | }, 26 | "homepage": "https://github.com/josippapez/react-pdf-html#readme", 27 | "dependencies": { 28 | "@react-pdf/renderer": "^3.4.4" 29 | }, 30 | "peerDependencies": { 31 | "react": ">=16.8.0", 32 | "react-dom": ">=16.8.0" 33 | }, 34 | "devDependencies": { 35 | "@babel/preset-env": "^7.24.7", 36 | "@rollup/plugin-babel": "^6.0.4", 37 | "@rollup/plugin-commonjs": "^26.0.1", 38 | "@types/react": "^18.3.3", 39 | "@types/react-dom": "^18.3.0", 40 | "babel-core": "^6.26.3", 41 | "babel-runtime": "^6.26.0", 42 | "microbundle": "^0.15.1", 43 | "prettier": "^3.3.2", 44 | "react": "^18.3.1", 45 | "react-dom": "^18.3.1", 46 | "rollup": "^4.18.0", 47 | "rollup-plugin-sass": "^1.13.0", 48 | "rollup-plugin-typescript2": "^0.36.0", 49 | "typescript": "^5.5.2" 50 | }, 51 | "source": "src/index.ts", 52 | "main": "dist/index.js", 53 | "module": "dist/index.esm.mjs", 54 | "umd:main": "dist/index.umd.js", 55 | "unpkg": "dist/index.umd.js", 56 | "types": "dist/index.d.ts", 57 | "files": [ 58 | "dist" 59 | ], 60 | "exports": { 61 | ".": { 62 | "types": "./dist/index.d.ts", 63 | "require": "./dist/index.js", 64 | "import": "./dist/index.esm.mjs", 65 | "umd": "./dist/index.umd.js" 66 | }, 67 | "./package.json": "./package.json" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-pdf-html 2 | 3 | `react-pdf-html` is a package that provides components that can render `@react-pdf/renderer` components as HTML components. 4 | 5 | This provides an alternative for rendering PDF on the fly to check the final PDF and it enables having one source of truth for your PDF templates/components. 6 | 7 | ## Installation 8 | 9 | You can install `react-pdf-html` using npm/yarn/bun: 10 | 11 | ```bash 12 | npm install @rawwee/react-pdf-html 13 | ``` 14 | 15 | ## Usage 16 | 17 | To use `react-pdf-html`, you need to import the components you want to use from the package and render them in your React application as you would when using `@react-pdf/renderer`. Here's an example: 18 | 19 | #### Example when displaying it in application 20 | 21 | ```tsx 22 | import React from "react"; 23 | // this replaces @react-pdf/renderer components 24 | import { Document, Image, Page, View } from "@rawwee/react-pdf-html"; 25 | 26 | const MyDocument = () => ( 27 | 28 | 29 | Hello, World! 30 | 31 | 32 | ); 33 | 34 | // You can then render the component as an HTML component 35 | const App = () => ; 36 | 37 | export default App; 38 | ``` 39 | 40 | In this example, we're rendering a simple PDF document with a single page that contains the text "Hello, World!". We're using the PDFViewer component from react-pdf-html to render the PDF as an HTML component. 41 | 42 | As you can see, you don't need the `@react-pdf/renderer`'s `PDFViewer` component to render the PDF (or `Document` component from `wojtekmaj/react-pdf`). 43 | 44 | #### Example when rendering the PDF 45 | 46 | The package provides a useful hook called `usePDFComponentsAreHTML()` which enables (or disables) the rendering of the components as HTML components. This is useful when you want to render the PDF conditionally (e.g. when you want to download the PDF). 47 | 48 | ```tsx 49 | export const PDFDownload = ({ PdfInstance, show, closeModal }: Props) => { 50 | const { isHTML, setHtml } = usePDFComponentsAreHTML(); 51 | const [download, setDownload] = useState(false); 52 | 53 | useEffect(() => { 54 | if (show) { 55 | // triggers the rendering of the PDF template component 56 | // which is defined as: 57 | // const TemplateNotHtml = useCallback(() => CVTemplate(pdfData), [isHTML, pdfData]); 58 | setHtml(false); 59 | } 60 | }, [show]); 61 | 62 | return ( 63 | { 67 | setHtml(true); 68 | setDownload(false); 69 | }} 70 | > 71 |
72 |

Download CV

73 |
74 | {PdfInstance && 75 | (download ? ( 76 | : <>} 79 | fileName={`${cvName}.pdf`} 80 | > 81 | {({ blob, url, loading, error }) => { 82 | if (loading) return "Loading document..."; 83 | return "Download now!"; 84 | }} 85 | 86 | ) : ( 87 | 93 | ))} 94 |
95 |
96 |
97 | ); 98 | }; 99 | ``` 100 | As you can see, we're using the `usePDFComponentsAreHTML()` hook to set the `isHTML` value to `false` when the modal is shown. This triggers the rendering of the PDF template component. When the modal is closed, we set the `isHTML` value to `true` which triggers the rendering of the PDF as an HTML component. 101 | 102 | ## Used by 103 | You can check the usage for this package in the following projects: 104 | - [CV Maker [josippapez/CV-Maker]](https://github.com/josippapez/CV-Maker) 105 | 106 | ## Contributing 107 | 108 | Contributions are welcome! If you find a bug or want to add a new feature, please open an issue or submit a pull request on GitHub. 109 | 110 | ## License 111 | 112 | react-pdf-html is licensed under the MIT License. 113 | -------------------------------------------------------------------------------- /src/Types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentProps, 3 | PageProps, 4 | SVGPresentationAttributes, 5 | SourceObject, 6 | Style, 7 | TextProps, 8 | ViewProps, 9 | } from "@react-pdf/types"; 10 | import { PropsWithChildren } from "react"; 11 | 12 | export type { Style }; 13 | 14 | export interface PropsView extends PropsWithChildren { 15 | style?: Style | Style[]; 16 | } 17 | 18 | export interface PropsText extends PropsWithChildren { 19 | style?: Style | Style[]; 20 | } 21 | 22 | export interface PropsImage extends ImageWithSrcProp { 23 | style?: Style | Style[]; 24 | } 25 | 26 | export interface PropsPage extends PropsWithChildren { 27 | style?: Style | Style[]; 28 | } 29 | 30 | export interface PropsLink extends PropsWithChildren { 31 | style?: Style | Style[]; 32 | } 33 | 34 | export interface PropsSVG extends PropsWithChildren { 35 | style?: Style | Style[]; 36 | } 37 | 38 | export type PropsRect = PropsWithChildren; 39 | 40 | export type PropsPath = PropsWithChildren; 41 | 42 | export type PropsG = PropsWithChildren; 43 | 44 | export type PropsLine = PropsWithChildren; 45 | 46 | export type PropsStop = PropsWithChildren; 47 | 48 | export type PropsLinearGradient = PropsWithChildren; 49 | 50 | export type PropsDefs = PropsWithChildren; 51 | 52 | export type PropsDocument = PropsWithChildren; 53 | 54 | export type PropsPolygon = React.PropsWithChildren; 55 | 56 | export type { Style as StylePDF }; 57 | 58 | export type fontWeight = 59 | | number 60 | | "thin" 61 | | "hairline" 62 | | "ultralight" 63 | | "extralight" 64 | | "light" 65 | | "normal" 66 | | "medium" 67 | | "semibold" 68 | | "demibold" 69 | | "bold" 70 | | "ultrabold" 71 | | "extrabold" 72 | | "heavy" 73 | | "black"; 74 | 75 | // react-pdf/ types 76 | interface NodeProps { 77 | id?: string; 78 | style?: Style | Style[]; 79 | /** 80 | * Render component in all wrapped pages. 81 | * @see https://react-pdf.org/advanced#fixed-components 82 | */ 83 | fixed?: boolean; 84 | /** 85 | * Force the wrapping algorithm to start a new page when rendering the 86 | * element. 87 | * @see https://react-pdf.org/advanced#page-breaks 88 | */ 89 | break?: boolean; 90 | /** 91 | * Hint that no page wrapping should occur between all sibling elements following the element within n points 92 | * @see https://react-pdf.org/advanced#orphan-&-widow-protection 93 | */ 94 | minPresenceAhead?: number; 95 | } 96 | 97 | interface BaseImageProps extends NodeProps { 98 | /** 99 | * Enables debug mode on page bounding box. 100 | * @see https://react-pdf.org/advanced#debugging 101 | */ 102 | debug?: boolean; 103 | cache?: boolean; 104 | } 105 | 106 | interface ImageWithSrcProp extends BaseImageProps { 107 | src: SourceObject; 108 | } 109 | 110 | interface ImageWithSourceProp extends BaseImageProps { 111 | source: SourceObject; 112 | } 113 | 114 | interface LinkProps extends NodeProps { 115 | /** 116 | * Enable/disable page wrapping for element. 117 | * @see https://react-pdf.org/components#page-wrapping 118 | */ 119 | wrap?: boolean; 120 | /** 121 | * Enables debug mode on page bounding box. 122 | * @see https://react-pdf.org/advanced#debugging 123 | */ 124 | debug?: boolean; 125 | src: string; 126 | } 127 | 128 | interface SVGProps extends NodeProps { 129 | /** 130 | * Enables debug mode on page bounding box. 131 | * @see https://react-pdf.org/advanced#debugging 132 | */ 133 | debug?: boolean; 134 | width?: string | number; 135 | height?: string | number; 136 | viewBox?: string; 137 | preserveAspectRatio?: string; 138 | } 139 | 140 | interface RectProps extends SVGPresentationAttributes { 141 | style?: SVGPresentationAttributes; 142 | x: string | number; 143 | y: string | number; 144 | width: string | number; 145 | height: string | number; 146 | rx?: string | number; 147 | ry?: string | number; 148 | } 149 | 150 | interface PropPolygon { 151 | style?: React.CSSProperties | SVGPresentationAttributes; 152 | points: string; 153 | } 154 | interface PathProps extends SVGPresentationAttributes { 155 | style?: SVGPresentationAttributes; 156 | d: string; 157 | } 158 | 159 | interface LineProps extends SVGPresentationAttributes { 160 | style?: SVGPresentationAttributes; 161 | x1: string | number; 162 | x2: string | number; 163 | y1: string | number; 164 | y2: string | number; 165 | } 166 | 167 | interface StopProps { 168 | offset: string | number; 169 | stopColor: string; 170 | stopOpacity?: string | number; 171 | } 172 | 173 | interface LinearGradientProps { 174 | id: string; 175 | x1: string | number; 176 | x2: string | number; 177 | y1: string | number; 178 | y2: string | number; 179 | } 180 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Defs, 3 | Document, 4 | G, 5 | Image, 6 | Line, 7 | LinearGradient, 8 | Link, 9 | Page, 10 | Path, 11 | Polygon, 12 | Rect, 13 | Stop, 14 | Svg, 15 | Text, 16 | View, 17 | } from "@react-pdf/renderer"; 18 | import React, { CSSProperties, FC, useState } from "react"; 19 | import { 20 | PropsDefs, 21 | PropsDocument, 22 | PropsG, 23 | PropsImage, 24 | PropsLine, 25 | PropsLinearGradient, 26 | PropsLink, 27 | PropsPage, 28 | PropsPath, 29 | PropsPolygon, 30 | PropsRect, 31 | PropsSVG, 32 | PropsStop, 33 | PropsText, 34 | PropsView, 35 | Style, 36 | fontWeight, 37 | } from "./Types"; 38 | 39 | let isHtml = true; 40 | 41 | export const usePDFComponentsAreHTML = () => { 42 | const [html, setHtml] = useState(isHtml); 43 | 44 | isHtml = html; 45 | 46 | return { 47 | isHTML: html, 48 | setHtml, 49 | }; 50 | }; 51 | 52 | const fontWeightConverter = (fontWeight?: fontWeight) => { 53 | if (typeof fontWeight === "number") return fontWeight; 54 | switch (fontWeight) { 55 | case "thin": 56 | case "hairline": 57 | return 100; 58 | case "ultralight": 59 | case "extralight": 60 | return 200; 61 | case "light": 62 | return 300; 63 | case "normal": 64 | return 400; 65 | case "medium": 66 | return 500; 67 | case "semibold": 68 | case "demibold": 69 | return 600; 70 | case "bold": 71 | return 700; 72 | case "ultrabold": 73 | case "extrabold": 74 | return 800; 75 | case "heavy": 76 | case "black": 77 | return 900; 78 | default: 79 | return 400; 80 | } 81 | }; 82 | 83 | const adjustStyles = (style: Style) => { 84 | if (!style) return; 85 | 86 | Object.keys(style).forEach(key => { 87 | if (key === "paddingVertical") { 88 | style.paddingTop = style[key]; 89 | style.paddingBottom = style[key]; 90 | } else if (key === "paddingHorizontal") { 91 | style.paddingLeft = style[key]; 92 | style.paddingRight = style[key]; 93 | } else if (key === "fontWeight") { 94 | style.fontWeight = fontWeightConverter(style[key]); 95 | } 96 | return style; 97 | }); 98 | }; 99 | const mergeStylesIntoOne = (styles: Style[]) => { 100 | const mergedStyle: Style = {}; 101 | 102 | if (!styles[0]) return mergedStyle; 103 | 104 | styles.forEach(style => { 105 | Object.keys(style).forEach(key => { 106 | mergedStyle[key as keyof Style] = style[key as keyof Style]; 107 | }); 108 | }); 109 | return mergedStyle; 110 | }; 111 | 112 | export const CustomView: FC = ({ children, style, ...rest }) => { 113 | const isDebug = rest.debug; 114 | if (isHtml) { 115 | let newStyle = style; 116 | if (Array.isArray(style)) { 117 | newStyle = mergeStylesIntoOne(style) as { 118 | [key: string]: string; 119 | }; 120 | } 121 | 122 | adjustStyles(newStyle as { [key: string]: string }); 123 | 124 | let styles: CSSProperties = { 125 | display: "flex", 126 | position: "relative", 127 | isolation: "isolate", 128 | left: 0, 129 | right: 0, 130 | 131 | ...(newStyle as { [key: string]: string }), 132 | }; 133 | 134 | if (isDebug) { 135 | styles.border = "1px solid red"; 136 | } 137 | 138 | return
{children}
; 139 | } 140 | 141 | if (Array.isArray(style)) { 142 | style = [ 143 | { 144 | display: "flex", 145 | flexDirection: "column", 146 | position: "relative", 147 | left: 0, 148 | right: 0, 149 | }, 150 | ...style, 151 | ]; 152 | } else { 153 | style = { 154 | display: "flex", 155 | flexDirection: "column", 156 | position: "relative", 157 | left: 0, 158 | right: 0, 159 | ...style, 160 | }; 161 | } 162 | return ( 163 | 164 | {children} 165 | 166 | ); 167 | }; 168 | 169 | export const CustomText: FC = ({ children, style, ...rest }) => { 170 | let newStyle = style; 171 | if (Array.isArray(style)) { 172 | newStyle = mergeStylesIntoOne(style) as { 173 | [key: string]: string; 174 | }; 175 | } 176 | 177 | if (isHtml) { 178 | const isDebug = rest.debug; 179 | adjustStyles(newStyle as { [key: string]: string }); 180 | 181 | let styles: CSSProperties = { 182 | whiteSpace: "break-spaces", 183 | position: "relative", 184 | border: isDebug ? "1px solid red" : "none", 185 | ...(newStyle as { [key: string]: string }), 186 | }; 187 | 188 | if (isDebug) { 189 | styles.border = "1px solid red"; 190 | } 191 | 192 | return
{children}
; 193 | } 194 | 195 | return ( 196 | 203 | {children} 204 | 205 | ); 206 | }; 207 | 208 | export const CustomImage: FC = ({ style, ...rest }) => { 209 | if (isHtml) { 210 | let newStyle = style; 211 | if (Array.isArray(style)) { 212 | newStyle = mergeStylesIntoOne(style) as { 213 | [key: string]: string; 214 | }; 215 | } 216 | adjustStyles(newStyle as { [key: string]: string }); 217 | return ( 218 | 222 | ); 223 | } 224 | return ; 225 | }; 226 | 227 | export const CustomPage: FC = ({ style, children, ...rest }) => { 228 | if (isHtml) { 229 | let newStyle = style; 230 | if (Array.isArray(style)) { 231 | newStyle = mergeStylesIntoOne(style) as { 232 | [key: string]: string; 233 | }; 234 | } 235 | adjustStyles(newStyle as { [key: string]: string }); 236 | return ( 237 |
249 | {children} 250 |
251 | ); 252 | } 253 | return ( 254 | 255 | {children} 256 | 257 | ); 258 | }; 259 | 260 | export const CustomLink: FC = ({ children, style, ...rest }) => { 261 | if (isHtml) { 262 | let newStyle = style; 263 | if (Array.isArray(style)) { 264 | newStyle = mergeStylesIntoOne(style) as { 265 | [key: string]: string; 266 | }; 267 | } 268 | adjustStyles(newStyle as { [key: string]: string }); 269 | return ( 270 | 271 |
{children}
272 |
273 | ); 274 | } 275 | return ( 276 | 277 | {children} 278 | 279 | ); 280 | }; 281 | 282 | export const CustomG: FC = ({ children, ...rest }) => { 283 | if (isHtml) { 284 | let adjustedRest: React.SVGProps & { style?: CSSProperties } = 285 | { 286 | ...rest, 287 | strokeLinejoin: "round", 288 | style: { 289 | strokeLinejoin: "round", 290 | }, 291 | }; 292 | 293 | return {children}; 294 | } 295 | return {children}; 296 | }; 297 | 298 | export const CustomPath: FC = ({ children, ...rest }) => { 299 | if (isHtml) { 300 | let adjustedRest: 301 | | React.SVGProps 302 | | { style?: CSSProperties } = { 303 | ...rest, 304 | strokeLinejoin: "round", 305 | style: { 306 | ...rest.style, 307 | strokeLinejoin: "round", 308 | }, 309 | }; 310 | 311 | return {children}; 312 | } 313 | return {children}; 314 | }; 315 | 316 | export const CustomRect: FC = ({ children, ...rest }) => { 317 | if (isHtml) { 318 | let adjustedRest: React.SVGProps & { 319 | style?: CSSProperties; 320 | } = { 321 | ...rest, 322 | strokeLinejoin: "round", 323 | style: { 324 | ...rest.style, 325 | strokeLinejoin: "round", 326 | }, 327 | }; 328 | 329 | return {children}; 330 | } 331 | return {children}; 332 | }; 333 | 334 | export const CustomSVG: FC = ({ children, ...rest }) => { 335 | if (isHtml) { 336 | const style = { 337 | left: 0, 338 | right: 0, 339 | ...rest.style, 340 | }; 341 | return ( 342 | 348 | {children} 349 | 350 | ); 351 | } 352 | return {children}; 353 | }; 354 | 355 | export const CustomPolygon: FC = ({ style, ...rest }) => { 356 | if (isHtml) { 357 | let newStyle = { 358 | ...style, 359 | strokeLinejoin: "round", 360 | } as React.CSSProperties | undefined; 361 | 362 | return ; 363 | } 364 | return ; 365 | }; 366 | 367 | export const CustomDefs: FC = ({ children, ...rest }) => { 368 | if (isHtml) { 369 | return {children}; 370 | } 371 | return {children}; 372 | }; 373 | 374 | export const CustomLine: FC = ({ children, ...rest }) => { 375 | if (isHtml) { 376 | let adjustedRest: React.SVGProps & { 377 | style?: CSSProperties; 378 | } = { 379 | ...rest, 380 | strokeLinejoin: "round", 381 | style: { 382 | ...rest.style, 383 | strokeLinejoin: "round", 384 | }, 385 | }; 386 | return {children}; 387 | } 388 | return {children}; 389 | }; 390 | 391 | export const CustomStop: FC = ({ children, ...rest }) => { 392 | if (isHtml) { 393 | return {children}; 394 | } 395 | return {children}; 396 | }; 397 | 398 | export const CustomLinearGradient: FC = ({ 399 | children, 400 | ...rest 401 | }) => { 402 | if (isHtml) { 403 | return {children}; 404 | } 405 | return {children}; 406 | }; 407 | 408 | export const CustomDocument: FC = ({ children, ...rest }) => { 409 | if (isHtml) { 410 | return ( 411 |
422 | {children} 423 |
424 | ); 425 | } 426 | return {children}; 427 | }; 428 | 429 | export { 430 | CustomDefs as Defs, 431 | CustomDocument as Document, 432 | CustomG as G, 433 | CustomImage as Image, 434 | CustomLine as Line, 435 | CustomLinearGradient as LinearGradient, 436 | CustomLink as Link, 437 | CustomPage as Page, 438 | CustomPath as Path, 439 | CustomPolygon as Polygon, 440 | CustomRect as Rect, 441 | CustomStop as Stop, 442 | CustomSVG as Svg, 443 | CustomText as Text, 444 | CustomView as View, 445 | }; 446 | --------------------------------------------------------------------------------