As a hobby project I am exploring the ways to save a web page (HTML) as image, mostly programatically using c/c++/javascript/java. Till now I have come across the following ways:
Get the IHTMLElement of page body and use it to query for IHTMLElementRender and then use its DrawToDC method (Ref: http://www.codeproject.com/KB/IP/htmlimagecapture.aspx ). But the problem is that it did not work for all the pages (mostly pages having embedded iframes).
Another way which i can think of is to use some web browser component and when the pages is fully loaded then capture it using BitBlt (Ref: http://msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx ). But the problem is that the page I have requested may be longer than my screen size and it will not fit into the web browser component.
Any direction/suggestion to resolve above issues or an alternative approach is greatly appreciated.
If you use Python, there's pywebshot and webkit2png. Both of them have some dependencies, though.
Edit: Oops, Python is not in your list of preferred languages. I'll leave this answer here anyway, because you said "mostly" and not "exclusively".
Another (somewhat roundabout) option would be to run a server like Tomcat and use Java to call a command-line tool to take a screenshot. Googling for "command line screenshot windows" comes up with some reasonable-looking possibilities. Besides running a server, though, I don't know a good way to run local executables from javascript. This method would make it cross-browser, though, which is a plus (just make an ajax call to the script when you want a screenshot).
Unfortunately I don't actually know how to deploy war files. It might be more trouble to use Tomcat; I mentioned it because Java was a preferred language. It would be fairly simple to run XAMPP and use this PHP snippet, and you wouldn't really have to learn php:
<?php
exec("/path/to/exec args");
?>
EDIT
You know, I'm not sure that really answers your question. It's one way, but it's coming at it from the JavaScript end rather than the scripting end. If you want to do it via scripting, you could always use Selenium. It supports capturing screenshots of an entire page, and can be controlled via Java.
Well finally able to crack it by going through these two articles:
http://www.codeproject.com/KB/GDI-plus/WebPageSnapshot.aspx [c# code - IE]
http://www.codeproject.com/KB/graphics/IECapture.aspx [c++ & GDI - IE]
Can't share the code, but the above two articles will give you the best possible solution.
Also have a look at:
https://addons.mozilla.org/en-US/firefox/addon/3408/ [firefox + javascript]
Above things are still ok. BUT not guaranteed to work always. Check the below link:
How do I render the scrollable regions of a canvas with IViewObject::Draw?
If you are OK using javascript for it, I suggest going with phantomjs
Example from http://fcargoet.evolix.net/
var page = new WebPage(),
address = 'http://dev.sencha.com/deploy/ext-4.0.7-gpl/examples/feed-viewer/feed-viewer.html';
page.viewportSize = {
width : 800,
height : 600
};
// define the components we want to capture
var components = [{
output : 'feed-viewer-left.png',
//ExtJS has a nice component query engine
selector : 'feedpanel'
},{
output : 'feed-viewer-preview-btn.png',
selector : 'feeddetail > feedgrid > toolbar > cycle'
},{
output : 'feed-viewer-collapsed.png',
//executed before the rendering
before : function(){
var panel = Ext.ComponentQuery.query('feedpanel')[0];
panel.animCollapse = false; // cancel animation, no need to wait before capture
panel.collapse();
},
selector : 'viewport'
}];
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
/*
* give some time to ExtJS to
* - render the application
* - load asynchronous data
*/
window.setTimeout(function () {
components.forEach(function(component){
//execute the before function
component.before && page.evaluate(component.before);
// get the rectangular area to capture
/*
* page.evaluate() is sandboxed
* so that 'component' is not defined.
*
* It should be possible to pass variables in phantomjs 1.5
* but for now, workaround!
*/
eval('function workaround(){ window.componentSelector = "' + component.selector + '";}')
page.evaluate(workaround);
var rect = page.evaluate(function(){
// find the component
var comp = Ext.ComponentQuery.query(window.componentSelector)[0];
// get its bounding box
var box = comp.el.getBox();
// box is {x, y, width, height}
// we want {top, left, width, height}
box.top = box.y;
box.left = box.x;
return box;
});
page.clipRect = rect;
page.render(component.output);
});
// job done, exit
phantom.exit();
}, 2000);
}
});
Related
I want to create a plugin that adds a video on the current slide in an open instance of Open Office Impress by specifying the location of the video automatically. I have successfully added shapes to the slide. But I cannot find a way to embed a video.
Using the .uno:InsertAVMedia I can take user input to choose a file and it works. How do I want to specify the location of the file programmatically?
CONCLUSION:
This is not supported by the API. Images and audio can be inserted without user intervention but videos cannot be done this way. Hope this feature is released in subsequent versions.
You requested information about an extension, even though the code you are using is quite different, using a file stream reader and POI.
If you really do want to develop an extension, then start with one of the Java samples. An example that uses Impress is https://wiki.openoffice.org/wiki/File:SDraw.zip.
Inserting videos into an Impress presentation can be difficult. First be sure you can get it to work manually. The most obvious way to do that seems to be Insert -> Media -> Audio or Video. However many people use links to files instead of actually embedding the file. See also https://ask.libreoffice.org/en/question/1898/how-to-embed-video-into-impress-presentation/.
If embedding is working for your needs and you want to automate the embedding by using an extension (which seems to be what your question is asking), then there is a dispatcher method called InsertAVMedia that does this.
I do not know offhand what the parameters are for the call. See https://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=61127 for how to look up parameters for dispatcher calls.
EDIT
Here is some Basic code that inserts a video.
sub insert_video
dim document as object
dim dispatcher as object
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(document, ".uno:InsertAVMedia", "", 0, Array())
end sub
From looking at InsertAVMedia in sfx.sdi, it seems that this call does not take any parameters.
EDIT 2
Sorry but InsertVideo and InsertImage do not take parameters either. From svx.sdi it looks like the following calls take parameters of some sort: InsertGalleryPic, InsertGraphic, InsertObject, InsertPlugin, AVMediaToolBox.
However according to https://wiki.openoffice.org/wiki/Documentation/OOoAuthors_User_Manual/Getting_Started/Sometimes_the_macro_recorder_fails, it is not possible to specify a file for InsertObject. That documentation also mentions that you never know what will work until you try it.
InsertGraphic takes a FileName parameter, so I would think that should work.
It is possible to add an XPlayer on the current slide. It looks like this will allow you to play a video, and you can specify the file's URL automatically.
Here is an example using createPlayer: https://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=57699.
EDIT:
This Basic code works on my system. To play the video, simply call the routine.
sub play_video
If Video_flag = 0 Then
video =converttoURL( _
"C:\Users\JimStandard\Downloads\H264_test1_Talkinghead_avi_480x360.avi")
Video_flag = 1
'for windows:
oManager = CreateUnoService("com.sun.star.media.Manager_DirectX")
' for Linux
' oManager = CreateUnoService("com.sun.star.media.Manager_GStreamer")
oPlayer = oManager.createPlayer( video )
' oPlayer.CreatePlayerwindow(array()) ' crashes?
'oPlayer.setRate(1.1)
oPlayer.setPlaybackLoop(False)
oPlayer.setMediaTime(0.0)
oPlayer.setVolumeDB(GetSoundVolume())
oPlayer.start() ' Lecture
Player_flag = 1
Else
oPlayer.start() ' Lecture
Player_flag = 1
End If
End Sub
We can successfully convert an SVG into an image with Batik, however, I need to convert a whole HTML div, with SVG implemented within, along with its CSS presentation code, into an image.
Is there any modules / support within Batik or some other Java API for achieving this?
Selenium library for Java may help you. It can run a browser (ie, chrome, firefox, etc.) in background mode, and you can load an HTML and take a snapshot of the content.
Although it's designed for testing and automation, it's the only way I can offer to you.
Hope it helps.
http://www.seleniumhq.org/
We had the same problem, and we solved it by spawning an PhantomJS process.
Phantom takes an JavaScript file that will instruct its headless browser what to do.
You can wait until the page is fully loaded and then you can print the output into the console as a data URI.
Below is a very simple example from my PhantomJS scripts:
var page = require( "webpage" ).create();
var options = JSON.parse( phantom.args[ 0 ] );
page.open( options.url, "POST", decodeURIComponent( options.payload || "" ), function( status ) {
if ( status === "fail" ) {
phantom.exit( 1 );
}
var contents = page.renderBase64( "png" );
require( "system" ).stderr.write( contents );
});
This is not an easy task as what you are asking is the process called "html rendering" and is basically what browsers try to implement correctly for over 2 decades.
If the CSS you need rendered is fairly simple (no CSS3, no fancy stuff, etc.), then there is a high chance that one of the open-source renderers would be able to handle that (PhantomJS as an example). See #gustavohenke answer for more details.
If the CSS is moderately complex and if you are able to modify it if needed - then there are some fast but non-free renderers, like PrinceXML and DocRaptor.
If the CSS could be very complex and you are not able to make it simpler - then the only option would be to render it in a real browser. You can use Selenium for that as it has a way of running the browser, rendering your HTML in it and "screenshotting" the result all in automated fashion. See #Jorgeblom answer for more details.
So the problem I am having is getting my like/unlike button to refresh with ajax in my Ruby on Rails app, here is my code:
app/views/_comment.html.haml
- likes = comment.likes
%div.comment{id: "comment-#{comment.id}"}
.comment-avatar
.medium-user-avatar.avatar-canvas
- if comment.user.avatar_url
= image_tag comment.user.avatar_url(:medium)
- else
%span.medium-user-initials.initials-decoration
= comment.user.avatar_initials
%span.comment-username= link_to(comment.user_name, "#")
%span.comment-body~ markdown(comment.body)
.comment-time
= time_ago_in_words(comment.created_at) + " ago"
- if can? :like, comment
= " · "
- if likes.find_by_user_id(current_user.id).nil?
= link_to "Like", like_comment_path(comment), method: :post, remote: true
- else
= link_to "Unlike", unlike_comment_path(comment), method: :post, remote: true
- if comment.user == current_user
= " · "
= link_to "Delete", comment_path(comment), method: :delete, remote: true,
:data => { :confirm => "Are you sure you want to delete this comment?" }
- if likes.count > 0
.comment-likes
- likers = likes.map { |like| link_to(like.user_name, "#") }
- if likers.length > 1
- likers = likers.slice(0, likers.length - 1).join(", ").concat(" and " + likers.slice(-1))
- else
- likers = likers[0]
= "Liked by #{likers}".html_safe
app/controllers/comments_controller.rb
class CommentsController < BaseController
load_and_authorize_resource
def destroy
destroy!{ discussion_url(resource.discussion ) }
end
def like
comment_vote = resource.like current_user
Event.comment_liked!(comment_vote)
#redirect_to discussion_url(resource.discussion )
render :partial => "like"
comment_likes
end
def unlike
resource.unlike current_user
#redirect_to discussion_url(resource.discussion)
render :partial => "unlike"
comment_likes
end
def comment_likes
render :partial => "comment_likes"
end
end
and then the .js.erb files for like, unlike and comment_likes:
app/views/_like.js.erb
$(".comment-time a#like").html("<%= escape_javascript(render('.comment-time a#like'")
app/views/_unlike.js.erb
$(".comment-time a#unlike").html("<%= escape_javascript(render('.comment-time a#unlike'")
app/views/_comment_likes.js.erb
$(".comment-likes a##").html("<%= escape_javascript(render('.comment-likes a##'")
Currently clicking like will update the database but will not show the changes until a page refresh, I just want to refresh the individual div's with ajax. A little more information about the div's could help so the ruby creates the html and contained in that as an example is or when already liked I just need to refresh these divs to show the latest from the database aswell as which contains http://localhost:3000/comments/7/unlike 500 (Internal Server Error)"
The rest of the scripting has been done in coffeescript if that matters? I read that the controller functions should use .js.erb so hope this isn't affecting it all. (Im sure my js.erb's are wrong)
I'm not sure if this is the main problem but one thing to fix would be the render statement in your .js.erb files.
The Rails render method requires as its argument the erb template or action to render. When you call:
render('.comment-time a#unlike')
Rails will try to find a template with the name ".comment-time a#unlike" somewhere in your view path which will probably raise some kind of error. Make sure you pay attention to the difference between what's happening in your javascript (in the client) in your application (on the server.)
So one way to fix this would be as follows. First check what part of the view you want to update, for simplicity's sake because you already have the _comment partial let's use that. Second figure out which part in the dom it should replace, in your case the div with the current comment's ID. We can then do the following:
$("#comment-<%= #comment.id %>").replace_html(
"<%= escape_javascript render('comments/comment', :comment => #comment) %>"
);
This will render the _comment.html.erb partial in app/views/comments, insert the result (escaped) into the javascript in your (dis-)like.js.erb and send that back to the browser to execute. The browser will then replace_html on the comment's div (indicated by the ID.)
Because we're replacing the whole comment div you can use the same method for both like and dislike. If you need to save bandwidth you could fine tune it to only re-render the like button itself, but for now this will work.
The problem is your Controller does not know how to respond to the ajax request. By default the render method will render html.erb or in your case html.haml templates.
Unless you do something like:
respond_to do |format|
format.js { comment_likes }
end
Now if an ajax requests comes in the _comments_likes.js.erb template will be rendered.
If you have more of these ajaxy type questions there are efforts being put into rewriting the ajax guides for rails.
And by the by, please consider cleaning up your view.
I am using Visualr http://googlevisualr.herokuapp.com/ with Rails and having a good amount of success creating dynamic charts. However, I am wondering if it's possible to allow the user to click on the column in a 'column chart' and be linked to a page? I am happy to know the java version if you aren't familiar with visualr.
Thanks!
It now is available!
There has recently been an update on this issue. Therefore I want to update this SO Q&A.
Resources:
Google Visualr Github Pull Request #39
Google Visualr Github Issue #36
Code example
xxx_controller.rb
#table = GoogleVisualr::Interactive::ColumnChart.new(g, options_g)
#table.add_listener("select", "function(e) {
EventHandler(e, chart, data_table)
}")
And then in a JS file e.g. app/assets/javascripts/application.js:
function EventHandler(e, chart, data) {
var selection = chart.getSelection();
if (selection.length > 0) {
var row = selection[0].row;
var department = data.getValue(row, 0);
alert(department + " | " + row)
}
}
Google Charts (whether you access them directly or via a wrapper gem like Visualr) are simple images, so the straight answer is "No", at least not without doing some work of your own. In order to achieve this you would need to place your own transparent clickable links (or divs or whatever) over the image, in the right place, to correspond to the columns that google generate in the image.
I'd imagine this would be tricky and error prone - it might actually be easier for you to just generate the columns yourself in html and css, using the data you would previously have sent to google to set the height (in %) of the columns. Then, each column would be a seperate html element and could link to whatever you want.
So, more control = more work. As usual :)
I am getting an error that is only reproducible in Firefox.
In IE, it always works.
I have an ASP.NET application that targets an applet. For most functionality, it works great, but when I run one function (a function that does a lot of different things), the methods on the java applet seems to be unavailable! When I call any functions on the java applet from Javascript code, I get the error : myApplet.myFunction is not a function .
After this has happened, I get the same error for everything that calls functions on the applet, also functionality that worked prior to this state.
The applet is not unloaded or crashed, which is proved by that the interactive functions in the applet still works. But if the applet calls a javascript function on the page that calls back to the applet, I get the same error!
Sometimes it works when I repeat the action several times.
This only happens on the public version of the web application. Not in my local web app that runs on development machine. The main difference is that the public one use login access.
Can anyone give me hint of what can be causing this?
To summairize whats happens: When I run a particular bit of javascript code, the API against the java applet becomes unabailable (Error: myApplet.myFunction is not a function).
Here is the function that causes this buggy state of the applet:
function ParseAndZoomToAddress (objString) {
var mapFrame
eval("var objArray = new Array(" + objString + ")")
if (objArray.length > 0) {
mapFrame = window.frames[0].frames['mapFrame']
if(!mapFrame) alert('mapFrame is null!')
else window.status = 'mapFrame is OK!'
var center_x = 0
var center_y = 0
if(objArray.length == 1) {
var rExp1 = /[G-P]/g
var rExp2 = /[Q-Z]/g
eval("var coordArray = new Array(" + objArray[0].replace(rExp1, '0x').replace(rExp2, ',0x') + ")")
if(coordArray.length == 2){
center_x = coordArray[0]
center_y = coordArray[1]
}else{
alert("Invalid ID " + objArray[0])
}
}else{
var center_x = objArray[1]
var center_y = objArray[2]
}
var objZoomScale = '2000' // document.SearchForm.Scale.value
var scale = mapFrame.m_yur - mapFrame.m_yll
if (objZoomScale != "") {
scale = ScaleToMeter(parseInt(objZoomScale),mapFrame.m_image_width)
}
var xll = center_x - (scale / 2.0)
var yll = center_y - 10
var xur = xll + scale
var yur = center_y + 10
center_x = (xll + xur) / 2.0
center_y = (yll + yur) / 2.0
center_x = Math.round(center_x * 1000) / 1000
center_y = Math.round(center_y * 1000) / 1000
mapFrame.ResetClipPolygon() //This calls applet methods
var lCenterSymbol = '5024,40,85,x,y,5'
var rExp = /x/gi
lCenterSymbol = lCenterSymbol.replace(rExp,center_x)
rExp = /y/gi
lCenterSymbol = lCenterSymbol.replace(rExp,center_y)
mapFrame.parent.m_CenterSymbolString = lCenterSymbol
mapFrame.ResetClipPolygon() //This calls applet methods
mapFrame.previewVisible()
mapFrame.mapVisible()
mapFrame.setMove(false) //This calls applet methods
mapFrame.ZoomToArea(xll, yll, xur, yur) //This calls applet methods
mapFrame.parent.m_CenterSymbolString = ""
}
}
Additionaly I can say that the applet is defined by the <applet> tag.
The applet is located inside a <div> tag that is hidden in javascript with style.display='none' and shown again with style.display='block'.
This is done during the 2 lines:
mapFrame.previewVisible()
mapFrame.mapVisible()
I don't know if this is the problem, but these function are called elsewhere as well where it doesn't cause this, but it might be combination of things.
I am happy to get hint's that will help me solve this, not an absolute solution.
I will accept the best hint as the answer.
Perhaps this is a timing issue. It could happen that the Firefox engine tries to execute the javascript code before the java applet has been properly loaded. To verify this add a delay before accessing the applet in javascript code. Even better use a try-catch block for your call and wrap it in a for-loop. If the call is successful exit the loop, otherwise add a delay and retry.
Edit: Based on your edits it seems that there isn't a timing issue. Since your javascript function is quite big, I would try to locate, where the problem first appears. Create a dummy applet method and call it inside your javascript method at various places. Find out after which statement the problem first appears. Also, if you haven't done that already, install Firebug and watch out for javascript errors.
I was suddenly able to reproduce it in my development environment, so it was easier to locate the problem. The problem was related to hiding/showing as I suspected, but I'm still not sure why it was a problem. It was probably some sort of timing issues as noted by kgiannakakis, so will credit him with the bounty reward.
The problem disappeared when commenting out these two lines:
mapFrame.previewVisible(); // appletDiv.style.display="none"
mapFrame.mapVisible(); // appletDiv.style.display="block"
The comment at end is basically what is done inside those functions with the div that contains the applet.
So far, so good.
The intention with the Visible functions are originally switching 2 tabs where one of them displays the applet (mapVisible), and the other displays a static image (previewVisible). If I click the preview tab, that basically runs previewVisible(), the appletDiv is hidden. If I then run my big nasty function (ParseAndZoomToAddress) then I get the error again, but this time it is somewhat expected, since the applet is hidden (and obviously FireFox does not like to work with hidden applets..).
Then I remember why I included these calls inside the ParseAndZoomToAddress function: I have to make sure the applet is visible before accessing it! So I try to re-introduce the call to mapFrame.mapVisible(), and guess what - it works! Even if the applet was hidden, it is available after the call to mapFrame.mapVisible().
Anyone have a conclusion to this?
My theory is that it causes problems to unload and reload the applet so close together in the same javascript function.
I came here through a search, as I had exactly the same problem in Firefox (not MSIE), on web pages that I knew I had not changed for years and that I knew had worked fine before in Firefox.
myApplet.myFunction is not a function
As it turns out, in my case the culprit is just having the Google Translate Tools Javascript code described at
http://translate.google.com/translate_tools?hl=en&layout=1&eotf=1&sl=bg&tl=en
After I had removed that snippet from my included Javascript file (included at the bottom of my applet page), myApplet.myFunction worked fine again!
It also fixed the mysterious problem that my applet would not always load when loading the web page. Maybe Google Translate Tools under the hood also fiddles with style.display or similar, but I did not further dig into that.
I suspect that many others who use Google Translate Tools on a Java applet page with Javascript controls are similarly affected.
This post seems to have similar problem. They claim they fixed some HTML syntactic error and then it worked in Firefox.
I had a similar problem.
We fixed it by putting the <applet></applet> inside a <div> that has no display:none style.
JavaScript in Firefox cannot see functions in an applet that is inside a div that is not displayed.