Forum How do I...?

jQuery: setTimeout called too late

Cescone
I'm trying to run jQuery to replace the URLs of some background images in an file, prior to conversion to PDF using Prince.

Well, if I use the usual $(document).ready(function() { ... }; to execute the scripts, I get the following warning, and nothing is executed:
prince: warning: setTimeout called too late


Without it I get the following error and again nothing is executed:
prince: https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js:3: error: TypeError: value is not an object


I'm using the latest Prince build available for windows (prince-20161018-win64.zip) via the cli for testing.

I tried to replace jQuery 3.1.1 with older 2.x and 1.x with the same results. I also replaced all my URL replacing code with a simple background color change via jQuery ($('body').css('background-color','#FF0000');). If I enclose it with $(document).ready(function() { ... }; I get the same warning as above and nothing happens. If I use it alone it works.

I also use lodash core 4.16.4, but loading it doesn't change the above behaviour.

Any idea of what is going on?

Edited by Cescone

hallvord
Hi,
I've investigated a bit..
First, one of the tricky things for Prince when implementing JS is to decide when to stop. Unlike a web page that can behave like an application and allow JavaScript to run, animate stuff, update the page etc. as long as it's open. Prince's goal is to get something rendered into a static file, and it's a really hard problem to find the best time to say "OK, JS on this page is probably done doing all it needs to do", stop the scripts and finish the rendering.

One of the steps Prince takes, is to not allow nested timeout threads. If a script which was started with setTimeout() *itself* calls this method, Prince won't honor it but print a warning. This is the warning you see.

It happens because jQuery.ready() (or rather jQuery's defer/promise implementation) uses several timeout calls. Amusingly, one of them happens because jQuery mistakes Prince for an older IE since Prince doesn't support document.readyState:

// Catch cases where $(document).ready() is called
// after the browser event has already occurred.
// Support: IE <=9 - 10 only
// Older IE sometimes signals "interactive" too soon
if ( document.readyState === "complete" ||
	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {

	// Handle it asynchronously to allow scripts the opportunity to delay ready
	window.setTimeout( jQuery.ready );


(Source code from non-minimised jQuery 3.1.1)
Fixing that won't really solve it though - the error will still happen here:

window.setTimeout( process );

and the processing won't happen. So there are more layers of setTimeout() in the Promises implementation?!

It would be nice to get this working without $.ready() (just adding the script that triggers the whole thing at the end of the page might work?) but I'm afraid I can't help you much with the other error without more information. Could you share a file, either here or sent to my E-mail @hallvord.com (prepend same username as in this forum)?

Announcement: repos for tests/utils

Edited by hallvord

Cescone
Thanks for the detailed answer!

I've emailed you HTML I'm using for testing.

hallvord
Hi Cescone, thanks for sending the file. Let's not worry about the document.ready / setTimeout issue, I'm pretty sure you can drop the ready() stuff. However, we have a bit of a problem when jQuery does this:

if ( elem.getClientRects().length ) {


That's what causes the TypeError because Prince doesn't support the Element.getClientRects() method.

Now, because of Prince's rather esoteric goal and processing I've been told it gets really complicated to add support for such a method. The developers know they should do it for compatibility, but it's tricky. Basically, this method reads dimensions of an element. When Prince runs JavaScript, the page has not been laid out (because Prince does not support reflow and many JavaScripts will update the DOM - you'll get the result you want in more cases than not by running the script before layout and rendering. But that means elements don't really have dimensions to report..

It is easy to make this error go away by faking support for the method - but I have no idea if it will give you the desired result or not.. I didn't see the generated PDF change but I don't know what to look for. Anyway, to make sure the error doesn't interrupt jQuery execution, save this code to a script (getclientrects.js) and use the --script getclientrects.js argument to make Prince run it while processing the page.

Element.prototype.getClientRects = function(){
    return [];
};


How does that work for you?

Announcement: repos for tests/utils

Cescone
Well, this solved the JS error, but without it jQuey wont return the dimensions I need, and never will since you said they don't exist yet when the script is run.

So I rewrote the script without using jQuery, selecting the elements with document.querySelector().

Your info stating that Prince runs the scripts before applying the style sheet was valuable. I noticed that the dimensions returned were the original values in the style sheets, and not the rendered values (usually in pixels). It was a little bit more work to calculate the final pixel dimensions for the images, but in the end it worked.

Thanks again for all your (really detailed) help!

Edited by Cescone

Cescone
P.S. Is there any way for Prince to automatically reduce the image resolution to a requested DPI value?

I mean if I want to create a PDF with 100DPI resolution, a 10x10cm image used in the document should have a pixel size of approximately 400x400px, but AFAIK, if I have a 4000x4000px image in this page, Prince will use it as is.

This is what I'm trying to do with this script: reduce the size of the images sent to Prince to the required resolution.

P.P.S: can you confirm if Princes uses a base resolution is 72DPI? The values returned in JS for the size of an A4 page were 595.275590551181pt x 841.8897637795275pt, wich are the dimensions of an A4 page @ 72DPI.


Edited by Cescone

mikeday
CSS point units (pt) are 72dpi. Prince uses 96dpi for pixel units (px).

At the moment we don't have any method to automatically scale down images for a specific target resolution.