Question
Morgan Stanley
US
Last activity: 12 Jan 2017 12:19 EST
Calling external js while creating PDF
Hi,
We are using CKEditor ( as Rich Text editor) in Pega to allow users to write Math Functions. These Math functions (equations) are rendering proeprly on UI but when we are trying to create PDF or show the Editor in Read Only format, it is showing the source code of the equation.
Is there any way to load CkEditorMath.js file in the background so that PDF can print the Rendered equation not the source code?
-
Like (0)
-
Share this page Facebook Twitter LinkedIn Email Copying... Copied!
Pegasystems Inc.
IN
How you are generating the PDF ? Is it simply HTMLtoPDF activity ?
Morgan Stanley
US
Yeah we are using OOTB activity HTMLtoPDF to generate the PDF Doc
Pegasystems Inc.
GB
Also: is the JS using client-side (HTML5 Canvas maybe?) stuff to render the equations ?
If so: this is unlikely to work with HTMLTOPDF. (Which as far as I know does not call any Javascript engine whilst processing the input).
Is there an equivalent server-side library ( a JSP Tag Library or similar?) that can convert the content to (say) a image (a PNG/JPG etc?) - because that should work. (Although you'll still need to do some additional 'plumbing' to get this to work [you'll need to store the images somewhere that HTMLTOPDF can see them): there is a worked-example here: https://collaborate.pega.com/question/how-can-i-put-qr-code-print-reportexpexcel-or-pdf for doing a similar thing with server-generated images for QR codes.
Pegasystems Inc.
GB
Also see this: This gives some outlines of how to convert a client-side HTML5 Canvas (which I believe you are probably using here) to an image - by getting the clientside JS to POST the resultant data to the backend, which then converts to an image format.
This would probably require a fair bit of customization though.
Updated: 10 Jan 2017 10:25 EST
Morgan Stanley
US
I guess the link is missing, can you please share it again
Pegasystems Inc.
GB
Oops sorry: yes it was : here it is again : http://stackoverflow.com/questions/13198131/how-to-save-a-html5-canvas-as-image-on-a-server
Pegasystems Inc.
GB
So I can confirm that HTMLTOPDF is NOT able to run the clientside Javascript and render the Canvas Object.
I built a 'Rule-Obj-HTML' rule with the following contents:
<html>
<head>
<title>Test Canvas Draw</title>
</head>
<body>
This is a HTML5 Canvas test
<br/>
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;">
</canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var half_height=c.height/2;
ctx.beginPath();
ctx.moveTo(0, half_height);
for(x=0;x < c.width;x++) {
ctx.lineTo( x, half_height+half_height*Math.sin(x/c.height*Math.PI) );
}
ctx.stroke();
var b64data=c.toDataURL().substring( c.toDataURL().indexOf(",")+1 );
alert( b64data );
</script>
</body>
</html>
If I do a 'Show-Stream' from an Activity on this HTML rule (or simply a 'preview' from the rule form itself), I can see the result is correctly rendered on the browser:
So I can confirm that HTMLTOPDF is NOT able to run the clientside Javascript and render the Canvas Object.
I built a 'Rule-Obj-HTML' rule with the following contents:
<html>
<head>
<title>Test Canvas Draw</title>
</head>
<body>
This is a HTML5 Canvas test
<br/>
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;">
</canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var half_height=c.height/2;
ctx.beginPath();
ctx.moveTo(0, half_height);
for(x=0;x < c.width;x++) {
ctx.lineTo( x, half_height+half_height*Math.sin(x/c.height*Math.PI) );
}
ctx.stroke();
var b64data=c.toDataURL().substring( c.toDataURL().indexOf(",")+1 );
alert( b64data );
</script>
</body>
</html>
If I do a 'Show-Stream' from an Activity on this HTML rule (or simply a 'preview' from the rule form itself), I can see the result is correctly rendered on the browser:
I can even get the Javascript to convert this to a binary (base64 encoded) version of this: (that's what the 'alert' is doing in the HTML above).
HTMLTOPDF (which actually uses the library 'PD4ML') is NOT able to render the Canvas (that is: it isn't able to run the Javascript which is used to draw on the Canvas).
Just to make sure : I tested this , and here's a screenshot of the PDF - no image :-(
It seems this is a general problem with HTML5 Canvas; and PD4ML is no exception - in fact see this (quite old) post on their Forums for instance:
If you can find (like I mentioned before) a Server-Side (Java-based) Javascript Interpretter (like 'Rhino' : https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino) which is also able to render a 'Canvas' object (which I'm not sure Rhino can on its own).
Or: if you can build another PRPC Activity which takes a single Parameter (the B64 data); and returns a ByteArray of the image; you could (perhaps) use that in conjunction with the Canvas code.
Basically: you would run your HTML on the browser; it would render the Canvas; it would then have to send the base64 encoded data back-to-the server; the server would need to store (or 'cache' I guess) the image data in a way that HTMLTOPDF could then retrieve it)....Again; it should work, but this has quite a number of 'moving parts' (especially if you have *multiple* Canvas objects in your HTML; and additionally it *has* to use a Browser at some point to generate the B64 data in the first place; so this would NOT work for any background/batch jobs; unless you use Pega Robotics of course....[which could allow you to fire up a browser automatically]).
Other Notes: We could even dynamically replace all the 'canvas' objects in the DOM with a rendered IMG object (using inline 'data:') - there is an example of this here (using JQuery): http://georgebohnisch.com/dynamically-generate-replace-html5-canvas-elements-img-elements/
But this is still no good for HTMLTOPDF of course - it still needs a way of rendering the Canvas Javascript.
[EDIT: even though the mechanism above [replacing all the canvas objects with img objects] would not directly help HTMLTOPDF - you could still then send the entire (re-rendered version) DOM back to a back-end service which WOULD be able to render it ; as the IMG tags would be 'self-contained' with all the B64 Data of all the images - probably quite a 'heavy' HTML file though) ].
Pegasystems Inc.
GB
I have managed to create a very simple Proof of Concept here: I can now send UP an HTML page FROM the browser; which allows HTMLTOPDF to convert images originally generated as clientside 'canvas' objects.
Caveats:
1. This isn't particularly 'PRPC-ish'; I'm sure somebody else can chip in here and re-write the bits using more standard PRPC bits (I've got a hard-coded 'form' object for instance).
2. This probably won't scale to very large images; since we using Javascript to essentially re-write the contents of the Canvas object with an inline base64 image format.
3. The way I'm doing this is a bit odd: I'm 'stashing' the (newly altered) DOM in a textarea/html form, and then sending up the entire HTML DOM back to PRPC for conversion - but this also includes the textarea/form itself ! (this is just a 'feature'/'bug' currently; I'm sure this can be figured out by somebody else).
Here's the working solution; firstly I create a simple wrapper Activity that combines calls to '@baseclass.HTMLTOPDF' and 'Code-Pega-PDF.View'; so that it both converts to PDF and then streams the result back in one go:
It's just called 'ConvertToPDF'; I'm using 'Pass current parameter page' for both calls here - and also I'm 'mirroring' a subset of Parameters from both HTMLTOPDF (Markup, and PDFDocument) and View (PDFName and [already used by HTMLTOPDF] PDFDocument):
I have managed to create a very simple Proof of Concept here: I can now send UP an HTML page FROM the browser; which allows HTMLTOPDF to convert images originally generated as clientside 'canvas' objects.
Caveats:
1. This isn't particularly 'PRPC-ish'; I'm sure somebody else can chip in here and re-write the bits using more standard PRPC bits (I've got a hard-coded 'form' object for instance).
2. This probably won't scale to very large images; since we using Javascript to essentially re-write the contents of the Canvas object with an inline base64 image format.
3. The way I'm doing this is a bit odd: I'm 'stashing' the (newly altered) DOM in a textarea/html form, and then sending up the entire HTML DOM back to PRPC for conversion - but this also includes the textarea/form itself ! (this is just a 'feature'/'bug' currently; I'm sure this can be figured out by somebody else).
Here's the working solution; firstly I create a simple wrapper Activity that combines calls to '@baseclass.HTMLTOPDF' and 'Code-Pega-PDF.View'; so that it both converts to PDF and then streams the result back in one go:
It's just called 'ConvertToPDF'; I'm using 'Pass current parameter page' for both calls here - and also I'm 'mirroring' a subset of Parameters from both HTMLTOPDF (Markup, and PDFDocument) and View (PDFName and [already used by HTMLTOPDF] PDFDocument):
IMPORTANT: I also needed to tick the 'Allow direct invocation from the client or a service' on the Security Tab to get this to work. (Which should be taken into account for security reasons for any Production Apps; note this option still requires login credentials to run through).
This means I can call the Activity now using a URL like this:
http://prpchost:port/prweb?pyActivity=OBQ6DY-GCS-Work.ConvertToPDF&Markup=hello&PDFName=test.pdf
In the case above - the 'HTML' is actually just the text 'hello' - and this sucessfully returns a converted PDF containing that text.
Now I altered my 'CanvasTest' Stream Rule to the following; I'm now including JQuery (not strictly necessary but handy for what we are doing; and its OOTB in newer PRPCs); and creating two Canvas objects with Javascript. We then dynamically alter the DOM to swap canvas objects for IMG objects; and stash the whole DOM in a 'textarea'; which is located in a HTML Form. The Form just posts the data to the Activity above - the result is a PDF containing the images. (you can just 'preview' the HTML Rule to use it).
Here's the HTML Rule Contents in full: (actually most of the code is used to draw pretty pictures for testing with).
<html>
<head>
<title>Test Canvas Draw</title>
<pega:static type="script" app="webwb">
<pega:file name="pzpega_jquery_1.9.1.js" />
</pega:static>
<script type="text/javascript">
function draw_sine_wave(canvasId) {
var c=$("#"+canvasId)[0];
var ctx = c.getContext("2d");
var half_height=c.height/2;
ctx.beginPath();
ctx.moveTo(0, half_height);
for(x=0;x < c.width;x++) {
ctx.lineTo( x, half_height+half_height*Math.sin(x/c.height*Math.PI) );
}
ctx.stroke();
}
function draw_kaleidoscope(canvasId) {
var c=$("#"+canvasId)[0];
var ctx = c.getContext("2d");
var half_width=c.width/2;
var half_height=c.height/2;
for (i=0;i<500;i++) {
x=Math.floor( (Math.random() * half_width ) + 1 );
y=Math.floor( (Math.random() * half_height ) + 1 );
ctx.fillRect(x,y,1,1);
ctx.fillRect(c.width-x,y,1,1);
ctx.fillRect(c.width-x,c.height-y,1,1);
ctx.fillRect(x,c.height-y,1,1);
}
}
</script>
</head>
<body>
<div id="main">
HTML5 Canvas test
<br/>
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;"></canvas>
<br/>
<canvas id="myCanvas2" width="200" height="100" style="border:1px solid #000000;"></canvas>
<br/>
<form id="data" action="/prweb" method="POST">
<input type="text" name="pyActivity" id="pyActivity" value="OBQ6DY-GCS-Work.ConvertToPDF"/>
<input type="text" name="PDFName" id="PDFName" value="test.pdf" size="20"/>
<br/>
<textarea class="Markup" name="Markup" id="Markup" width="20" height="100"></textarea>
<input type="submit" value="ConvertToPDF"/>
</form>
</div>
<script type="text/javascript">
draw_sine_wave('myCanvas');
draw_kaleidoscope('myCanvas2');
// see: http://georgebohnisch.com/dynamically-generate-replace-html5-canvas-elements-img-elements/
$(document).ready( function(e)
{
$('canvas').each( function(e)
{
var image = new Image();
image.src = this.toDataURL("image/png");
$(this).replaceWith(image);
$("textarea#Markup").val( $('div#main')[0].outerHTML );
});
});
</script>
</body>
</html>
And this looks like this on-screen:
The ugly form stuff is basically the parameters for calling our Activity; you can see part of the text area has now contains the HTML we will send up to PRPC (once the 'ConvertToPDF' button is pressed).
Pressing the button results in a PDF, that looks like this:
I'll attach the text of the RULE HTM as a text file also; as well as an example PDF that was created.
Hope this helps - the principle should work for any canvas-based Javascript stuff - like the Mathematical equations you are using.
PS: this was all done on PRPC722.
Cheers
John
Pegasystems Inc.
GB
I tweaked this such that the HTML form is now missed out of the HTML that is sent for conversion (I'm actually unsure what I did) - but I include the updated code here for reference !
PDF now looks like this (I added in some 'smiley' code to confirm that the colour image work - and also because it made me happy to see the PDF without the form being on there!)
Pegasystems Inc.
GB
Specifically for your question using the CKEditor - look like there is a flag for the editor that will do the rendering to base64 images: see here : http://ckeditor.com/forums/CKEditor/Convert-images-to-image-data-code
I don't know whether this flag works with the version of the CKEditor shipped with PRPC - and I don't know (without further research) about you would go about altering the shipped version of the CKEditor with PRPC.
Somebody else may be able to fill in the blanks here though !
Morgan Stanley
US
Thanks PritJ.
I am already replacing the OOTB text editors to include images and math formulas. Let me try implement the above mentioned solution. Thank you so much.
Thanks
Faheem