I need to have two different headers in my pdf generated using html2pdf from iText.
Currently I'm 'printing' the headers by using a implemetation of IEventHandler. Like this:
public virtual void HandleEvent(Event #event)
{
PdfDocumentEvent docEvent = (PdfDocumentEvent)#event;
PdfDocument pdf = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();
int pageNumber = pdf.GetPageNumber(page);
Rectangle pageSize = page.GetPageSize();
// Creates drawing canvas
PdfCanvas pdfCanvas = new PdfCanvas(page);
Canvas canvas = new Canvas(pdfCanvas, pageSize);
try
{
if (typeof(Paragraph) == element.GetType()) // This is an IElement object
{
Paragraph p = (Paragraph)element;
if (p.GetChildren().Count > 0)
{
p.SetWidth(pageSize.GetWidth() - iLeftMargin - iRightMargin).SetMarginLeft(iLeftMargin);
canvas.Add(p);
}
}
canvas.Close();
pdfCanvas.Release();
}
catch (System.Exception)
{
}
}
Now I need to check if the current page has a specific text (or anything) so I can change the text inside my element object and then print a different header.
It would be really nice if the TagWorker get processed alogside IEventHandler, but it is sequential, and I can't know when i need to change the text in my header.
Related
I am using iText7 to build a table of contents for my document. I know all the section names before I start, but don't know what the page numbers will be. My current process is to create a table on the first page and create all the Link objects with generic text "GO!". Then as I add sections I add through the link objects and update the text with the page numbers that I figured out as I created the document.
However, at the end, what gets written out for the link is "GO!", not the updated page number values I set as I was creating the rest of the document.
I did set the immediateFlush flag to false when I created the Document.
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
List<Link>links = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]>notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
links = new ArrayList<>();
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
links.add(link);
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document)
throws Exception {
int count = 0;
for (String[] note : notes) {
int numberOfPages = pdfDocument.getNumberOfPages();
Link link = links.get(count);
link.setText(""+(numberOfPages+1));
Paragraph noteText = new Paragraph(note[2]);
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
}
Quite more work needs to be done compared to the code you have now because the changes to the elements don't take any effect once you've added them to the document. Immediate flush set to false allows you to relayout the elements, but that does not happen automatically. The way you calculate the current page the paragraph will be placed on (int numberOfPages = pdfDocument.getNumberOfPages();) is not bulletproof because in some cases pages might be added in advance, even if the content is not going to be placed on them immediately.
There is a very low level way to achieve your goal but with the recent version of iText (7.1.15) there is a simpler way as well, which still requires some work though. Basically your use case is very similar to target-counter concept in CSS, with page counter being the target one in your case. To support target counters in pdfHTML add-on we added new capabilities to our layout module which are possible to use directly as well.
To start off, we are going to tie our Link elements to the corresponding Paragraph elements that they will point to. We are going to do it with ID property in layout:
link.setProperty(Property.ID, String.valueOf(count));
noteText.setProperty(Property.ID, String.valueOf(count));
Next up, we are going to create custom renderers for our Link elements and Paragraph elements. Those custom renderers will interact with TargetCounterHandler which is the new capability in layout module I mentioned in the introduction. The idea is that during layout operation the paragraph will remember the page on which it was placed and then the corresponding link element (remember, link elements are connected to paragraph elements) will ask TargetCounterHandler during layout process of that link element which page the corresponding paragraph was planed on. So in a way, TargetCounterHandler is a connector.
Code for custom renderers:
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
Don't forget to assign the custom renderers to their elements:
link.setNextRenderer(new CustomLinkRenderer(link));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
Now, the other thing we need to do it relayout. You already set immediateFlush to false and this is needed for relayout to work. Relayout is needed because on the first layout loop we will not know all the positions of the paragraphs, but we will already have placed the links on the pages by the time we know those positions. So we need the second pass to use the information about page numbers the paragraphs will reside on and set that information to the links.
Relayout is pretty straightforward - once you've put all the content you just need to call a single dedicated method:
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
One caveat is that for now you also need to subclass the DocumentRenderer since there is an additional operation that needs to be done that is not performed under the hood - propagation of the target counter handler to the root renderer we will be using for the second layout operation:
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
document.setRenderer(new CustomDocumentRenderer(document, false));
And now we are done. Here is our visual result:
Complete code looks as follows:
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]> notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
document.setRenderer(new CustomDocumentRenderer(document, false));
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
link.setProperty(Property.ID, String.valueOf(count));
link.setNextRenderer(new CustomLinkRenderer(link));
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document) {
int count = 0;
for (String[] note : notes) {
Paragraph noteText = new Paragraph(note[2]);
noteText.setProperty(Property.ID, String.valueOf(count));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
}
In Unity, one of my MonoBehaviours has a field pointing to another object (a ScriptableObject). If I double-click that field, I can see the fields of that object. How do I render those fields into the top-level MonoBehaviour's property drawer?
In picture form
What I have
(double-click the element)
What I want
I have my own [CustomEditor] component, but I can't quite get it to work right; stuff like this:
SerializedProperty activityStack = serializedObject.FindProperty("activityStack");
EditorGUILayout.PropertyField(activityStack.GetArrayElementAtIndex(0));
just renders the "Element 0 (Idle Activity)" bit and not the actual contents of the reference.
Because the default PropertyField for a ScriptableObject is just the one you get: A UnityEngine.Object reference field like for GameObject and Components and other assets ;)
Of course you can implement what you want to achieve but that's a bit more complex and not really good for maintenance and I would not recommend it.
I don't know your ScriptableObject so here an example
public class ExampleSO : ScriptableObject
{
public int SomeInt;
[SerializeField] private string _someString;
}
and your MonoBehaviour e.g.
public class Example : MonoBehaviour
{
public List<ExampleSO> _SOList;
}
Then the editor could look like e.g.
using UnityEditor;
using UnityEngine;
// This is the namespace for the ReorderableList
using UnityEditorInternal;
[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
SerializedProperty _SOList;
Example _example;
MonoScript _script;
ReorderableList _list;
private void OnEnable()
{
// Link up the serializedProperty
_SOList = serializedObject.FindProperty("_SOList");
// get the casted target instance (only needed for drawing the script field)
_example = (Example) target;
// get the according script instance (only needed for drawing the script field)
_script = MonoScript.FromMonoBehaviour(_example);
// Set up the ReorderableList
_list = new ReorderableList(serializedObject, _SOList, true, true, true, true)
{
// What shall be displayed as header for the list?
drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, _SOList.displayName),
// How is each element displayed?
drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
// Get the element in the list (SerializedProperty)
var element = _SOList.GetArrayElementAtIndex(index);
// and draw the default object reference field
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent("Reference"));
// Check if an asset is referenced - if not we are done here
if (!element.objectReferenceValue) return;
// Otherwise get the SerializedObject for this asset
var elementSerializedObject = new SerializedObject(element.objectReferenceValue);
// and all the properties (SerializedProperty) of it you want to display
var someInt = elementSerializedObject.FindProperty("SomeInt");
var someString = elementSerializedObject.FindProperty("_someString");
// Similar to the OnInspectorGUI first load the current values into this serializedobject
elementSerializedObject.Update();
{
// Adding some indentation just to show that the following fields are actually belonging to the referenced asset
EditorGUI.indentLevel++;
{
rect = EditorGUI.IndentedRect(rect);
// shift down the rect by one line
rect.y += EditorGUIUtility.singleLineHeight;
// Draw the field for the Int
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), someInt);
// Shift down the rect another line
rect.y += EditorGUIUtility.singleLineHeight;
// Draw the string field
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), someString);
}
EditorGUI.indentLevel--;
}
// Write back the changed values and trigger the checks for logging dirty states and Undo/Redo
elementSerializedObject.ApplyModifiedProperties();
},
// How much vertical space should be reserved for each element?
elementHeightCallback = (int index) =>
{
// Get the elements serialized property
var element = _SOList.GetArrayElementAtIndex(index);
// by default we have only the asset reference -> single line
var lines = 1;
// if the asset is referenced adds space for the additional fields
if (element.objectReferenceValue) lines += 2; // or how many lines you'll need
return lines * EditorGUIUtility.singleLineHeight;
}
};
}
public override void OnInspectorGUI()
{
// draw th script field
DrawScriptField();
// Load the current values into the serializedObject
serializedObject.Update();
{
// let the ReorderableList do its magic
_list.DoLayoutList();
}
// Write back the changed values into the actual instance
serializedObject.ApplyModifiedProperties();
}
// Just draws the usual script field at the top of the Inspector
private void DrawScriptField()
{
EditorGUI.BeginDisabledGroup(true);
{
EditorGUILayout.ObjectField("Script", _script, typeof(Example), false);
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
}
}
Which results in the following Inspector. As you can see I opened the Isnpectors of the MonoBehaviour and two instances of the ExampleSO to show how the values are taken over to the actual instances
I am using HTMLConverter to convert html to PDF and trying to set some margins.
Existing code:
ConverterProperties props = new ConverterProperties();
props.setBaseUri("src/main/resources/xslt");
PdfDocument pdf = new PdfDocument(new PdfWriter(new FileOutputStream(dest)));
pdf.setDefaultPageSize(new PageSize(612F, 792F));
HtmlConverter.convertToPdf( html, pdf, props);
Can someone please advice on how to add margins? I used Document class to setMargin but not sure how does that make into the HTMLConverter's convertToPdf method.
Isn't it possible for you to use HtmlConverter#convertToElements method? It returns List<IElement> as a result and then you can add its elements to a document with set margins:
Document document = new Document(pdfDocument);
List<IElement> list = HtmlConverter.convertToElements(new FileInputStream(htmlSource));
for (IElement element : list) {
if (element instanceof IBlockElement) {
document.add((IBlockElement) element);
}
}
Another approach: just introduce the #page rule in your html which sets the margins you need, for example:
#page {
margin: 0;
}
Yet another solution: implement your own custom tag worker for <html> tag and set margins on its level. For example, to set zero margins one could create tag the next worker:
public class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (TagConstants.HTML.equals(tag.name())) {
return new ZeroMarginHtmlTagWorker(tag, context);
}
return null;
}
}
public class ZeroMarginHtmlTagWorker extends HtmlTagWorker {
public ZeroMarginHtmlTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
Document doc = (Document) getElementResult();
doc.setMargins(0, 0, 0, 0);
}
}
and pass it as a ConverterProperties parameter to Htmlconverter:
converterProperties.setTagWorkerFactory(new CustomTagWorkerFactory());
HtmlConverter.convertToPdf(new File(htmlPath), new File(pdfPath), converterProperties);
I would like to create a function in C# that takes a specific webpage and coverts it to a JPG image from within ASP.NET. I don't want to do this via a third party or thumbnail service as I need the full image. I assume I would need to somehow leverage the webbrowser control from within ASP.NET but I just can't see where to get started. Does anyone have examples?
Ok, this was rather easy when I combined several different solutions:
These solutions gave me a thread-safe way to use the WebBrowser from ASP.NET:
http://www.beansoftware.com/ASP.NET-Tutorials/Get-Web-Site-Thumbnail-Image.aspx
http://www.eggheadcafe.com/tutorials/aspnet/b7cce396-e2b3-42d7-9571-cdc4eb38f3c1/build-a-selfcaching-asp.aspx
This solution gave me a way to convert BMP to JPG:
Bmp to jpg/png in C#
I simply adapted the code and put the following into a .cs:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
public class WebsiteToImage
{
private Bitmap m_Bitmap;
private string m_Url;
private string m_FileName = string.Empty;
public WebsiteToImage(string url)
{
// Without file
m_Url = url;
}
public WebsiteToImage(string url, string fileName)
{
// With file
m_Url = url;
m_FileName = fileName;
}
public Bitmap Generate()
{
// Thread
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Capture
var browser = (WebBrowser)sender;
browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ScrollBarsEnabled = false;
m_Bitmap = new Bitmap(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.BringToFront();
browser.DrawToBitmap(m_Bitmap, browser.Bounds);
// Save as file?
if (m_FileName.Length > 0)
{
// Save
m_Bitmap.SaveJPG100(m_FileName);
}
}
}
public static class BitmapExtensions
{
public static void SaveJPG100(this Bitmap bmp, string filename)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(filename, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static void SaveJPG100(this Bitmap bmp, Stream stream)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(stream, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageDecoders();
foreach (var codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
// Return
return null;
}
}
And can call it as follows:
WebsiteToImage websiteToImage = new WebsiteToImage( "http://www.cnn.com", #"C:\Some Folder\Test.jpg");
websiteToImage.Generate();
It works with both a file and a stream. Make sure you add a reference to System.Windows.Forms to your ASP.NET project. I hope this helps.
UPDATE: I've updated the code to include the ability to capture the full page and not require any special settings to capture only a part of it.
Good solution by Mr Cat Man Do.
I've needed to add a row to suppress some errors that came up in some webpages
(with the help of an awesome colleague of mine)
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.ScriptErrorsSuppressed = true; // <--
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
}
...
Thanks Mr Do
Here is my implementation using extension methods and task factory instead thread:
/// <summary>
/// Convert url to bitmap byte array
/// </summary>
/// <param name="url">Url to browse</param>
/// <param name="width">width of page (if page contains frame, you need to pass this params)</param>
/// <param name="height">heigth of page (if page contains frame, you need to pass this params)</param>
/// <param name="htmlToManipulate">function to manipulate dom</param>
/// <param name="timeout">in milliseconds, how long can you wait for page response?</param>
/// <returns>bitmap byte[]</returns>
/// <example>
/// byte[] img = new Uri("http://www.uol.com.br").ToImage();
/// </example>
public static byte[] ToImage(this Uri url, int? width = null, int? height = null, Action<HtmlDocument> htmlToManipulate = null, int timeout = -1)
{
byte[] toReturn = null;
Task tsk = Task.Factory.StartNew(() =>
{
WebBrowser browser = new WebBrowser() { ScrollBarsEnabled = false };
browser.Navigate(url);
browser.DocumentCompleted += (s, e) =>
{
var browserSender = (WebBrowser)s;
if (browserSender.ReadyState == WebBrowserReadyState.Complete)
{
if (htmlToManipulate != null) htmlToManipulate(browserSender.Document);
browserSender.ClientSize = new Size(width ?? browser.Document.Body.ScrollRectangle.Width, height ?? browser.Document.Body.ScrollRectangle.Bottom);
browserSender.ScrollBarsEnabled = false;
browserSender.BringToFront();
using (Bitmap bmp = new Bitmap(browserSender.Document.Body.ScrollRectangle.Width, browserSender.Document.Body.ScrollRectangle.Bottom))
{
browserSender.DrawToBitmap(bmp, browserSender.Bounds);
toReturn = (byte[])new ImageConverter().ConvertTo(bmp, typeof(byte[]));
}
}
};
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
tsk.Wait(timeout);
return toReturn;
}
There is a good article by Peter Bromberg on this subject here. His solution seems to do what you need...
The solution is perfect, just needs a fixation in the line which sets the WIDTH of the image. For pages with a LARGE HEIGHT, it does not set the WIDTH appropriately:
//browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ClientSize = new Size(1000, browser.Document.Body.ScrollRectangle.Bottom);
And for adding a reference to System.Windows.Forms, you should do it in .NET-tab of ADD REFERENCE instead of COM -tab.
You could use WatiN to open a new browser, then capture the screen and crop it appropriately.
I have com.google.gwt.user.client.ui.HTML component which contains some anchors.
When the component is clicked I need distinguish the clicks on anchors from the clicks on rest of the content of the component.
Example:
htmlText = new HTML();
htmlText.setHTML("foo <a href=http://stackoverflow.com target=_blank>stackoverflow</a> bar");
htmlText.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
if (!anchorClicked(event)) doSomethingElse();
}
});
When the "stackoverflow" hyperlink is clicked, I want default behaviour - go to stackoverflow.com. When "foo" or "bar" is clicked, I want "doSomethingElse()" to be called.
Is there anyway to achieve that? What should be in the anchorClicked(e) method?
You ought to check if your EventTarget is the hyperlink element (or a child of hyperlink).
Lets say the id of your hyperlink is "corvus-link"
Element link = Document.get().getElementById("corvus-link");
Element trgt = Element.as(e.getNativeEvent().getEventTarget());
Then what you need to check in your case is:
link.isOrHasChild(trgt);
EDIT:
the method you've asked for would look something like this:
boolean anchorClicked(e)
{
Element link = Document.get().getElementById("corvus-link");
Element trgt = Element.as(e.getNativeEvent().getEventTarget());
return link.isOrHasChild(trgt);
}
This is solution I used. It's based on idea provided by #Amey. I had to modify his solution, because my HTML component may contain multiple anchors.
htmlText = new HTML();
htmlText.getElement().setId("HtmlTextArea");
...
private boolean anchorClicked(ClickEvent event) {
NodeList<Element> links = Document.get().getElementById("HtmlTextArea").getElementsByTagName("a");
Element target = Element.as(event.getNativeEvent().getEventTarget());
for (int i = 0; i < links.getLength(); i++) {
if (links.getItem(i).isOrHasChild(target)) {
return true;
}
}
return false;
}