Phanomjs not properly capturing screenshot of a web page - javascript

I am testing phantomjs for creating screenshots from a web page at my local end.
This is how HTML is rendering in localhost - Output
But on creating pdf from code, width is not setting properly in PDF, ie some text gets cut as shown below.
Following is the code I tired -
var url = 'http://localhost:5656';
page.open(url);
page.onLoadFinished = function() {
var pdfName = 'screen';
page.render(pdfName + ".pdf");
var height = page.evaluate(function() { return document.body.offsetHeight }),
width = page.evaluate(function() { return document.body.offsetWidth });
console.log(height,width);
};
I also tried these two properties but getting same result -
page.clipRect = { top: 0, left: 0, right: 0, width: 1286};
page.viewportSize = { width: 1024, height: 768};
Let me know what I am doing wrong here.
Output I am getting - pdf image I am getting

I solved the issue on my own. Following is the working code -
var url = 'http://localhost:5656';
page.open(url);
page.onLoadFinished = function() {
//page.viewportSize = { width: 1400, height: 1200 };
page.paperSize = {format: 'A4'};
var pdfName = 'screen';
page.render(pdfName + ".pdf");
var height = page.evaluate(function() { return document.body.offsetHeight }),
width = page.evaluate(function() { return document.body.offsetWidth });
console.log(height,width);
};
As you can see, following is the line that performed the magic for me -
page.paperSize = {format: 'A4'};
Hope it will help to the future visitors.

Related

Get natural dimensions in lazy loading mechanism

I need to adjust image sizes in a lazy loading mechanism. The final image paths are stored in attribute data-src, if lazy loading is enabled.
For optical reasons, I don't want to wait until lazy loading is done, so I've tried the following:
window.addEventListener('load', function() {
const aImages = document.getElementsByClassName('article-image');
for ( var i=0;i<aImages.length;i++ ) {
const wrp = aImages[i].parentElement;
var imgHeight, imgWidth;
// if lazyload
if ( aImages[i].getAttribute('data-src') ) {
var img = new Image();
img.onload = function() { imgHeight = this.naturalHeight; imgWidth = this.naturalWidth };
img.src = aImages[i].getAttribute('data-src');
} else {
imgHeight = aImages[i].naturalHeight;
imgWidth = aImages[i].naturalWidth;
}
var dim = calcAspectRatioFit(imgWidth, imgHeight, wrp.clientWidth, wrp.clientHeight);
console.log('['+i+']\n' + 'dim.width: '+dim.width + '\ndim.height: '+dim.height + '\ntypeof(wrp): '+typeof(wrp) + '\nwrp.clientWidth: '+wrp.clientWidth + '\nwrp.clientHeight: '+wrp.clientHeight + '\nimgWidth: '+imgWidth + '\nimgHeight: '+imgHeight);
aImages[i].style.setProperty('width', dim.width + 'px', 'important');
aImages[i].style.setProperty('height', dim.height + 'px', 'imoprtant');
}
});
Guess the problem is the onload-function. If I set a breakpoint between it and before calcAspectRatioFit(), everything works as excepted. Guess 'img' then has enough time to load.
Debug-Output from console.log():
[0]
dim.width: NaN
dim.height: NaN
typeof(wrp): object
wrp.clientWidth: 222
wrp.clientHeight: 192
imgWidth: undefined
imgHeight: undefined
Any ideas how to solve this?
// Edit 2:
Of course, the problem was, that the last four lines didn't wait for img.onload() to be executed. Thanks #Peter Krebs for this hint.
Here is a QnD solution which works fine. (Added anonymous wrapper fnc to copy i)
window.addEventListener('load', function() {
const aImages = document.getElementsByClassName('article-image');
const exec = function(i) {
const wrp = aImages[i].parentElement;
var dim = calcAspectRatioFit(aImages[i].naturalWidth, aImages[i].naturalHeight, wrp.clientWidth, wrp.clientHeight);
aImages[i].style.setProperty('width', dim.width + 'px');
aImages[i].style.setProperty('height', dim.height + 'px');
}
for ( var i=0;i<aImages.length;i++ ) {
if ( aImages[i].getAttribute('data-src') ) {
aImages[i].onload = (function(i) { return function() { exec(i) } })(i);
this.src = aImages[i].getAttribute('data-src');
} else { exec(i) }
}
});

Javascript code executes correctly when debugging, but doesn't work without browser Dev Tools running

I am trying to build a simple image uploader/gallery viewer for a webpage using PhotoSwipe. The gallery viewer plugin works without issues and needs an array (items in the code below) with the image src and the width and height per image. The function readImageDimensions() gets the data and later saves it to items.
When I'm debugging the code it runs flawlessly and the gallery gets the data, but if I skip the debugger the item variable has indexed two objects, but the src, w, h all have null values. Is it something in the code that runs asynchronously or otherwise disrupt the flow when the code isn't running through the debug process?
Code for uploading and presenting the images:
var thumbsDiv = "";
var items = [];
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('.file-uploader').addEventListener('change', onFilesSelected, false);
thumbsDiv = document.querySelector(".thumbs-div");
}
function onFilesSelected(e) {
if (!e.target.files || !window.FileReader) return;
thumbsDiv.innerHTML = "";
var width = 0;
var height = 0;
items = [];
function readImageDimensions(data) {
var image = new Image();
image.src = data;
width = image.width;
height = image.height;
}
var files = e.target.files;
var filesArr = Array.prototype.slice.call(files);
filesArr.forEach(function (f) {
if (!f.type.match("image.*")) {
return;
}
var reader = new FileReader();
reader.onloadend = function (e) {
var result = e.target.result;
var html = '<div class="img-wrap"><span class="close">×</span><img src="' + result + '"/>' + f.name + '</div>';
thumbsDiv.innerHTML += html;
readImageDimensions(result);
items.push({
src: result,
w: width,
h: height
});
}
reader.readAsDataURL(f);
});
}
Code for instantiating the gallery:
var openGallery = function () {
var pswpElement = document.querySelectorAll(".pswp")[0];
var options = {
index: 0
};
var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
}
The issue was in fact that the image data was loaded asynchronously, and the program tried to read the image dimensions before it was loaded. I used HMR's comment to further research the onload() handler and modified the readImageDimensions() like this:
function readImageDimensions(data) {
var image = new Image();
console.log(data);
image.onload = function () {
width = image.width;
height = image.height;
items.push({
src: data,
w: width,
h: height
});
}
image.src = data;
}
This way, the onload() gets "primed" with reading the width and height, and won't do that until the whole image data is loaded onto the browser cache. The code also pushes the data onto the items[] array and obviously I'll rename the method into something better, like setImageDataForGallery().

Running an async method in a syncronized manner

I have a simple for loop, which basically checks if the images are stored in the file system, if not, then download it and render the UI:
for (var t = 0; t < toJSON.length; t++) {
if (t < 3) {
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
image_url = toJSON[t];
//start
if (utilities.CheckIfImageExist(utilities.ExtractImageName(image_url))) {
var parent = Titanium.Filesystem.getApplicationDataDirectory();
var picture = Titanium.Filesystem.getFile(parent, 'pictures');
var picturePath = parent + 'pictures/';
Ti.API.info('picturePath: ' + picturePath);
var f = Titanium.Filesystem.getFile(picturePath, utilities.ExtractImageName(image_url));
var blob = f.read();
// here is saved blog file
console.log('Image already downloaded');
var width = blob.width;
var height = blob.height;
//crop so it fits in image view
if (width > height) {
view.image = ImageFactory.imageAsCropped(blob, {
width: height,
height: height,
x: 60,
y: 0
});
} else {
view.image = ImageFactory.imageAsCropped(blob, {
width: (width - 1),
height: (width - 1),
x: 60,
y: 0
});
}
} else {
//do new loop - async causing problems
alert('not downloaded');
// if image is not downloaded we will download it here
utilities.APIGetRequestImage(image_url, function (e) {
alert('begin downloaded');
var status = this.status;
if (status == 200) {
Ti.API.info(this.responseData);
//save to directory
utilities.SaveImageToDirectory(this.responseData, image_url);
//create view
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
var width = this.responseData.width;
var height = this.responseData.height;
//crop so it fits in image view
if (width > height) {
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
view.image = ImageFactory.imageAsCropped(this.responseData, {
width: height,
height: height,
x: 60,
y: 0
});
// $.scrollableView.addView(view);
viewArr.push(view);
} else {
view.image = ImageFactory.imageAsCropped(this.responseData, {
width: (width - 1),
height: (width - 1),
x: 60,
y: 0
});
viewArr.push(view);
//when t = 3, all views are put inside array, set image view
//if(t==3){
//}
}
}
}, function (err) {
alert('error downloading image');
});
}
}
}
The code, where it says "begin download" only executes after the for loop executes the first half of the IF statement (where it says "not downloaded"), by that t=3.
The for loop then executes the else statement, the trouble I have is that I need it to do it in a synchronized manner, as I am reliant on the t value to know which image to download and place in the view.
utilities.APIGetRequestImage(image_url, function(e) {
is a callback method which gets the file from the server and downloads it.
How can I get both methods to run concurrently?
Check it out:
for (var t = 0; t < toJSON.length; t++) {
if (t < 3) {
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
image_url = toJSON[t];
//start
if (utilities.CheckIfImageExist(utilities.ExtractImageName(image_url))) {
var parent = Titanium.Filesystem.getApplicationDataDirectory();
var picture = Titanium.Filesystem.getFile(parent, 'pictures');
var picturePath = parent + 'pictures/';
Ti.API.info('picturePath: ' + picturePath);
var f = Titanium.Filesystem.getFile(picturePath, utilities.ExtractImageName(image_url));
var blob = f.read();
// here is saved blog file
console.log('Image already downloaded');
var width = blob.width;
var height = blob.height;
//crop so it fits in image view
if (width > height) {
view.image = ImageFactory.imageAsCropped(blob, {
width: height,
height: height,
x: 60,
y: 0
});
} else {
view.image = ImageFactory.imageAsCropped(blob, {
width: (width - 1),
height: (width - 1),
x: 60,
y: 0
});
}
} else {
//do new loop - async causing problems
alert('not downloaded');
// if image is not downloaded we will download it here
utilities.APIGetRequestImage(image_url, (function (t, image_url) {
return function (e) { // <----- wrap callback function
alert('begin downloaded');
var status = this.status;
if (status == 200) {
Ti.API.info(this.responseData);
//save to directory
utilities.SaveImageToDirectory(this.responseData, image_url);
//create view
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
var width = this.responseData.width;
var height = this.responseData.height;
//crop so it fits in image view
if (width > height) {
var view = Titanium.UI.createImageView({
width: 320,
height: 310,
//top: 10
});
view.image = ImageFactory.imageAsCropped(this.responseData, {
width: height,
height: height,
x: 60,
y: 0
});
// $.scrollableView.addView(view);
viewArr.push(view);
} else {
view.image = ImageFactory.imageAsCropped(this.responseData, {
width: (width - 1),
height: (width - 1),
x: 60,
y: 0
});
viewArr.push(view);
//when t = 3, all views are put inside array, set image view
//if(t==3){
//}
}
}
};
})(t, image_url), function (err) {
alert('error downloading image');
});
}
}
}
By wrapping utilities.APIGetRequestImage callback, t and image_url are passed correctly.
When saying that you "need it to do it in a synchronized manner", then you don't actually. You say that you want to "get both methods to run concurrently", which requires asynchrony.
Just swap the check if(t==3) (which is always 3, and even if you did put a closure around it like #wachme suggested then it would just be 3 for the download that started last - not the one that ended last) for a check of the number of items you've received: if(viewArr.length==3). It will be 3 when all three callbacks are executed, and you can continue executing your tasks.

Resizing a hidden-frame

I am trying to load a webpage into a hidden-frame (sdk/frame/hidden-frame) and then render a screenshot of it. I can get the screenshot just fine, but the frame is not properly sized. The screenshot is rendered by using the following code
var hiddenFrames = require("sdk/frame/hidden-frame");
let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
onReady: function() {
this.element.contentWindow.location = "http://example.com";
let self = this;
this.element.addEventListener("DOMContentLoaded", function() {
var cnvs = self.element.contentDocument.createElement("canvas");
var width = self.element.contentDocument.body.clientHeight; //for some reason clientWidth is 0 for www.example.com
var height = self.element.contentDocument.body.clientHeight;
cnvs.width = width;
cnvs.height = height;
var ctx = cnvs.getContext("2d");
console.log(width+" "+height);
ctx.drawWindow(self.element.contentWindow, 0, 0, width, height, "rgb(255,255,255)");
console.log(cnvs.toDataURL());
}, true, true);
}
}));
I have tried changing the width and height of the iframe with
this.element.width = "1600";
this.element.height = "900";
changing the size of element.contentWindow with resizeTo(), and changing the size of the body. None of them seem to have an impact on the final screenshot, which looks like
Try adding this on the parent page after including the iframe (or script that includes the iframe).
$('iframe').load(function(){
$(this).css({
width: 1600,
height: 900
});
});

How do i deal with data:img elements populating in my resources?

Notice the data:img images... I dont know how to remove them from memory and document.images does not contain them...
I am writing a Chrome Extension. Its somewhat of a hybrid screenshot app and basically when a user scrolls it takes the new part of the screen that the user has scrolled to and appends it on to a master screenshot of the entire site (yeah i have used googles screenshot app). My code works, but when I pass the img from the extension to the content script its storing the data:img sources into somewhere in memory where I cant access. I have tried document.images and tested to see if they may be populated within but with no luck.
Heres the code if you are interested.
page.js
var ovr = 0;
var run = (window==window.top)?1:0;
if(run&&ovr){
function obj(){
window.onload = function(){
obj.conversion = .5;
obj.screenShot();
obj.startRecordingScroll();
obj.showShot();
};
}
obj.showShot = function(){
obj.fullShot = document.createElement('canvas');
obj.fullShot.zIndex = -100;
obj.fullShot.style.position = 'fixed';
obj.fullShot.style.top = '70px';
obj.fullShot.style.right = '20px';
obj.fullShot.style.backgroundColor = '#999999';
obj.fullShot.width = window.innerWidth*obj.conversion;
obj.fullShot.height = document.height*obj.conversion;
document.body.appendChild(obj.fullShot);
obj.ctx = obj.fullShot.getContext('2d');
};
obj.startRecordingScroll = function(){
document.onscroll = function(){
obj.scroll();
};
};
obj.scroll = function(){
var pagxoff = window.pageXOffset;
var pagyoff = window.pageYOffset;
alert(document.images.length);
console.log("scroll");
obj.screenShot();
};
obj.displayScreenShot = function(img){
console.log('displayScreenShot');
var ycur = window.pageYOffset;
var yMaxCur = window.innerHeight+window.pageYOffset;
var distance = yMaxCur - obj.lastMaxYSeen;
distance = Math.abs(distance);
if(!obj.firstRunShot){
obj.lastMinYSeen = window.pageYOffset;
obj.lastMinXSeen = window.pageXOffset;
obj.lastMaxYSeen = (window.innerHeight+window.pageYOffset);
obj.lastMaxXSeen = (window.innerWidth+window.pageXOffset);
var shot = document.createElement('img');
shot.src = img;
console.log(img);
shot.onload = function(){
obj.ctx.drawImage(
shot,
0, // 0 right
0, // 0 down
window.innerWidth, // viewport width
window.innerHeight, // viewport height
0, // 0 right
0, // 0 down
window.innerWidth*obj.conversion,
window.innerHeight*obj.conversion
);
};
obj.firstRunShot = true;
return;
}
if(obj.firstRunShot){
if(ycur>obj.lastMinYSeen){
obj.lastMinYSeen = window.pageYOffset;
obj.lastMinXSeen = window.pageXOffset;
obj.lastMaxYSeen = (window.innerHeight+window.pageYOffset);
obj.lastMaxXSeen = (window.innerWidth+window.pageXOffset);
var xshot = document.createElement('img');
xshot.src = img;
xshot.onload = function(){
obj.ctx.drawImage(
xshot,
0,
window.innerHeight-distance,
window.innerWidth,
distance,
0,
(obj.lastMaxYSeen-distance)*obj.conversion,
window.innerWidth*obj.conversion,
(distance)*obj.conversion
);
};
return;
}
}
};
obj.screenShot = function(){
var port = chrome.extension.connect({
name: "screenshot"
});
port.postMessage({
request: "screenshot"
});
port.onMessage.addListener(function (msg) {
obj.displayScreenShot(msg);
});
console.log('screenShot');
};
var builder = new obj();
}
You can track resources being added and manipulate all contents of these resources using chrome.devtools.inspectedWindow API's, if you want to delete some resource modify DOM by deleting node(s).
document.images does contain the resources.
The Resources panel lets you inspect resources that are loaded in the inspected page, are you loading your image into inspected page to show up here?
If you are trying to store image to local disk using FILE API, remember they are sand-boxed and cannot be accessed by other means.
References
chrome.devtools.inspectedWindow
Resources Panel
File API
Content Scripts

Categories