Open abollam opened 4 years ago
I'm also doing exactly this.
The way I'm doing it is to convert the html into json structure, then parse the json structure into @react-pdf elements. So you have a routine for that converts <p>
elements into paragraph Views and then places children <span>
elements as nested Text children etc.
That could be a problem if you're not sure what the incoming HTML is going to be - you'll have to do a complete parser for every html element.
Hi all, I am trying to render HTML inside the
<View>/<Text>
component while rendering the PDF inside the<PDFViewer>
using a Template.I'd like to render my custom HTML code shown below in my pdf
<p><span style="font-size: 24px;">Pre Interview Notes</span></p><p><br></p><p><strong style="font-size: 14px;"><u>This is a test Pre Interview Notes:</u></strong></p><p><br></p><p><em style="font-size: 14px;">The Guest requires a wheel chair for the show</em></p><p><br></p><p><br></p>
Environment
- Browser [e.g. Chrome ]:
- React-PDF version [1.6.8]:
- React version [e.g. 16.8.6]:
Same here, Im trying to render my data coming from CKEditor. Any Updates?
@tomgallagher don't suppose you have a basic example of how you're doing this? I need to render html from tinymce
@stevenbrown-85 it's a bit more involved than a basic example but I can give you some pointers.
HTML and the mirror react-pdf code are both tree structures, so you need two different functions to walk/save the tree and then rebuild it. You're looking at recursion to do this.
As you are using tinyMCE, you're in luck as you can limit the html structures that can be in your tree, you don't need to do every single HTML element. One thing to note is that tinyMCE onEditorChange
event just provides a list of HTML elements, so you need to provide a 'wrapper' parent element for the below to work. I use a document fragment.
Look at this function: dom-to-json, which is where I started. The tree structure is preserved by using a childNodes
array of sub-nodes. So you start with a document fragment with all the tinyMCE elements as childNodes
.
Then you will need to write your own json-to-pdf function. Mine has an entry point that looks like this
export const JsonToPdfComponent = (input, bodyTextColorString) => {
/*
the object was created with following values
{
nodeType: <integer>,
nodeTagName: <string>,
nodeName: <string>,
nodeValue: <string>,
childNodes: <array>
attributes: <array of attributes arrays>
}
*/
//first we have an error handler which will result in blank elements in the pdf rather than crashes
if (input === undefined) { console.error("JsonToPdfComponent: Undefined JSON Input"); return null; }
//create the object, either parsing a JSON string or just using an existing javascript object
let json = typeof input === 'string' ? JSON.parse(input) : input;
//define the node type
let nodeType = json.nodeType;
//define the tag type
let tagType = json.nodeTagName;
//then the construction process is different depending on the type of node
switch (nodeType) {
//MOST OF THE WORK DONE ON ELEMENT_NODES
case 1:
//then we need to create styled component views for each tag
switch(tagType) {
case 'h1':
//SEE EXAMPLE BELOW
return createH1ViewElement(json, bodyTextColorString);
case 'h2':
return createH2ViewElement(json, bodyTextColorString);
case 'h3':
return createH3ViewElement(json, bodyTextColorString);
case 'h4':
return createH4ViewElement(json, bodyTextColorString);
case 'h5':
return createH5ViewElement(json, bodyTextColorString);
case 'h6':
return createH6ViewElement(json, bodyTextColorString);
case 'strong':
return createStrongTextElement(json, bodyTextColorString);
case 'em':
return createEmphasisTextElement(json, bodyTextColorString);
case 'p':
return createParagraphTextElement(json, bodyTextColorString);
case 'span':
return createSpanTextElement(json, bodyTextColorString);
//we add a link tag only when the anchor tag has children, i.e. text
case 'a':
return createLinkElement(json, bodyTextColorString);
case 'img':
return createImageElement(json);
//special processing for ordered and unordered list components
case 'ol':
return createListComponent(json, bodyTextColorString, tagType);
case 'ul':
return createListComponent(json, bodyTextColorString, tagType);
//special processing for tables
case 'table':
return createTableComponent(json, bodyTextColorString);
//special processing for sup text
case 'sup':
return createSupComponent(json, bodyTextColorString);
default:
console.log(`No Processing for Tag ${tagType.toUpperCase()}`);
}
break;
//TEXT_NODE - we can just create the simple text item
case 3:
//this will return a null value if the text filtered for formatting characters has a length of zero
return createTextComponent(json.nodeValue);
default:
console.log("Skipping Node", json);
}
};
Note that I do some intermediate processing, so there's a crucial bit missing from this code, which is how to process the parent fragment element. You would do this by case 11
, where you would, for example, create the react-pdf Page
element using a routine like the following code sample and then all the childNodes
accordingly.
To rebuild, you use React.createElement
for each node, so for a simple h1
example:
import { View, Text } from '@react-pdf/renderer';
const createH1ViewElement = (node, bodyTextColorString) => {
return React.createElement(
//every html element should have its own view
View,
//add the special style props to the view, if you have any, from the JSON attributes array
{
//stops headers appearing at the bottom of the page
minPresenceAhead: 1,
},
//then we pass in the children of the json object, as children of the React element
React.createElement(
Text,
//add the special style props to the text element
{
//add the custom styles you have set for the pdf component
style: {...dynamicStyles.styledHeader1, color: bodyTextColorString},
},
//then we pass in the children of the json object = recursion or the end of recursion
node.childNodes.map(child => JsonToPdfComponent(child, bodyTextColorString))
)
);
};
That should get you started with basic text. Lists and tables are a bit tougher to get right.
Styles need to be converted from CSS to something understood by react-pdf. TinyMCE does most of the styling with span
elements.
If you have all the default fonts from tinyMCE, they will need to be loaded from somewhere. Images are also a pain as they have CORS issues.
Hope that helps a bit.
There's a big project potentially for react-pdf that converts any HTML string to pdf.
Tom
Thanks @tomgallagher - very helpful, much appreciated. I'll give this a bash over next few days and see how i get on. I have disabled fonts and images for tinymce so they wont be a problem - dealing with tables will be interesting though!
@stevenbrown-85, no problem and good luck! The lists and tables are a bit annoying but the worst bit was inline <sup>
elements. There's no good way to do those in @react-pdf, whatever you might read in related issues.
For later readers, I think that parsing HTML string is the hardest part and react-html-parser can help.
It will parse the string to ReactElement[] so that we can map the ReactElement to React-PDF elements.
For later readers, I think that parsing HTML string is the hardest part and react-html-parser can help.
It will parse the string to ReactElement[] so that we can map the ReactElement to React-PDF elements.
@iamacoderguy Hello, I do not understand how to map ReactElement to ReactPdfElements and I could not find anything on it. Could you please elaborate on that?
Here's my simple method. Html Render page
const parsedHtml = htmlParser(taskDescription);
return (
<View>
<Text>Description:</Text>
{parsedHtml}
</View>
)
html-parser:
import ReactHtmlParser from 'react-html-parser';
import { Text } from '@react-pdf/renderer';
import React from 'react';
export const htmlParser = (taskDescription: string | null) => {
let returnContentConst;
if (taskDescription) {
const parsedHtml = ReactHtmlParser(taskDescription);
parsedHtml.forEach(element => {
const type = element.type;
element.props.children.forEach((content: string) => {
switch (type) {
case 'p':
returnContentConst = (<Text>{content}</Text>)
break;
default:
returnContentConst = (<Text>{content}</Text>)
break;
}
})
})
return returnContentConst;
} else {
return returnContentConst;
}
}
@ArnasDickus Thanks! I appreciate it, Will try it once we start refactoring the code and adding more features.
Using node-html-parser
, I assembled a some helper functions and an <Html>
tag here: https://github.com/danomatic/react-pdf-html
Feedback would be welcome! I'm hoping it can become a standardized part of the react-pdf
library at some point. Ideally we can make it extensible so that users can implement custom tag renderers.
Here's my simple method. Html Render page
const parsedHtml = htmlParser(taskDescription); return ( <View> <Text>Description:</Text> {parsedHtml} </View> )
html-parser:
import ReactHtmlParser from 'react-html-parser'; import { Text } from '@react-pdf/renderer'; import React from 'react'; export const htmlParser = (taskDescription: string | null) => { let returnContentConst; if (taskDescription) { const parsedHtml = ReactHtmlParser(taskDescription); parsedHtml.forEach(element => { const type = element.type; element.props.children.forEach((content: string) => { switch (type) { case 'p': returnContentConst = (<Text>{content}</Text>) break; default: returnContentConst = (<Text>{content}</Text>) break; } }) }) return returnContentConst; } else { return returnContentConst; } }
Hey @ArnasDickus Thanks for the posting, but I am getting an error Error: Invalid element of type div passed to PDF renderer
.
Do you know why I am getting the error?
any updates on this?
@brxshncy
any updates on this?
I've continued working on https://github.com/danomatic/react-pdf-html. Please give it a try and let me know what you think!
@brxshncy
any updates on this?
I've continued working on https://github.com/danomatic/react-pdf-html. Please give it a try and let me know what you think!
Hello @danomatic , it says not available as an NPM package, how do I install this on an existing project? what specific files to download
@brxshncy I have not bundled and published as an npm package in the event that the author of react-pdf
was interested in including it. To use it at this time, just copy the files from src
in the repo and make sure you have these dependencies:
@diegomura are you interested in including https://github.com/danomatic/react-pdf-html as part of react-pdf
so that it can be maintained together or would you prefer that the Html
component be maintained separately?
Hi @danomatic, while we wait for @diegomura to consider including react-pdf-html
as part of react-pdf
, would it be possible for you to publish https://github.com/danomatic/react-pdf-html to npm? I have a use-case that would definitely benefit from npm availability. Thanks!
@thismax and @brxshncy I just published it to NPM:
@danomatic brilliant, thank you!
@danomatic You're Amazing!
Edit from @danomatic, a version that handles nested elements and elements if yall need it.
import ReactHtmlParser from "react-html-parser";
import { Text } from "@react-pdf/renderer";
import React from "react";
interface ParsedElementProps {
children: React.ReactNode;
[key: string]: any;
}
export const htmlParser = (taskDescription: string | null): JSX.Element => {
const parseElements = (elements: React.ReactNode): React.ReactNode[] => {
const returnContentConst: React.ReactNode[] = [];
React.Children.forEach(elements, (element) => {
if (typeof element === "string") {
// Handle string content
returnContentConst.push(<Text key={Math.random()}>{element}</Text>);
} else if (React.isValidElement(element)) {
const elementProps = element.props as ParsedElementProps;
const type = element.type;
const children = parseElements(elementProps.children);
switch (type) {
case "p":
returnContentConst.push(
<Text key={Math.random()}>{children}</Text>
);
break;
case "strong":
returnContentConst.push(
<Text
key={Math.random()}
style={{ fontFamily: "Helvetica-Bold", fontWeight: 600 }}
>
{children}
</Text>
);
break;
// Add more cases as needed for other HTML tags
default:
returnContentConst.push(
<Text key={Math.random()}>{children}</Text>
);
break;
}
}
});
return returnContentConst;
};
if (taskDescription) {
const parsedHtml = ReactHtmlParser(taskDescription);
const returnContentConst = parseElements(parsedHtml);
return <>{returnContentConst}</>;
} else {
return <></>;
}
};
Hopes this help!
@geralddevelop this is nice! I'm thinking of switching react-pdf-html
to use react-html-parser
for its parsing. I like it's transform
callback option.
Para los lectores posteriores, creo que analizar cadenas HTML es la parte más difĂcil y react-html-parser puede ayudar. Analizará la cadena como ReactElement[] para que podamos asignar ReactElement a los elementos React-PDF.
@iamacoderguy Hola, no entiendo cĂłmo mapear ReactElement a ReactPdfElements y no pude encontrar nada al respecto. ÂżPodrĂas explicarme más sobre eso?
you can do it in the following way, The only solution for this was to convert the hmtl to a react component with html-to-react.
`const HtmlToReactParser = HtmlToReact.Parser; const htmlToReactParser = new HtmlToReactParser();
const HtmlText = ({ html }) => { const reactElement = htmlToReactParser.parse(html); const convertElement = (element) => { if (!element) return null;
const { type, props } = element;
const { children } = props;
switch (type) {
case 'h1':
return <Text style={styles.heading}>{children.map(convertElement)}</Text>;
case 'h2':
return <Text style={styles.subheading}>{children.map(convertElement)}</Text>;
case 'p':
return <Text style={styles.paragraph}>{children.map(convertElement)}</Text>;
case 'b':
return <Text style={styles.bold}>{children.map(convertElement)}</Text>;
case 'i':
return <Text style={styles.italic}>{children.map(convertElement)}</Text>;
case 'u':
return <Text style={styles.underline}>{children.map(convertElement)}</Text>;
default:
return <Text>{children.map(convertElement)}</Text>;
}
};
return convertElement(reactElement); };`
Hi all, I am trying to render HTML inside the
<View>/<Text>
component while rendering the PDF inside the<PDFViewer>
using a Template.I'd like to render my custom HTML code shown below in my pdf
<p><span style="font-size: 24px;">Pre Interview Notes</span></p><p><br></p><p><strong style="font-size: 14px;"><u>This is a test Pre Interview Notes:</u></strong></p><p><br></p><p><em style="font-size: 14px;">The Guest requires a wheel chair for the show</em></p><p><br></p><p><br></p>
Environment