JWebEngine Programming Guide

The JWebEngine is a Java library. You can use it as an out-of-the box HTML renderer and editor or use its different features and interfaces for special purposes like JavaHelp embedding or thumbnail generation.

HTML Viewer

JWebEngine can be used to display HTML in your Java application, such as typical emails or help files. If you want to use JavaHelp, then read the chapter on JavaHelp. If all you want to do is render HTML using JWebEngine, simply register JWebEngine as the default HTML renderer in you application:

JEditorPane.registerEditorKitForContentType( "text/html", "com.inet.html.InetHtmlEditorKit" );

After running this code, any Swing JEditorPane/JTextPane in your application will render HTML using JWebEngine.

JEditorPane viewer = new JEditorPane();
parent.add( viewer );
viewer.setPage( htmlUrl );

or in case you want to display a string as HTML content

JEditorPane viewer = new JEditorPane();
parent.add( viewer );
viewer.setContentType( "text/html" );                           // required to display html strings
AbstractDocument doc = (AbstractDocument)viewer.getDocument();
doc.putProperty( Document.StreamDescriptionProperty, baseURL ); //Optional to solve relative links
viewer.setText( htmlUrl );

HTML Editor

To use JWebEngine as a WYSIWYG HTML editor, you may either write your own editor based on a JEditorPane or use the BaseEditor of JWebEngine. The BaseEditor is a full featured editor component including an editing toolbar, source code preview and popup menus. Implementing this editor is quite simple:

BaseEditor editor = new BaseEditor( true );
parent.add( editor );
editor.setPage( htmlUrl );

you can set the HTML content as a string here as well

BaseEditor editor = new BaseEditor( true );
parent.add( editor );
editor.setBase( baseURL ); //Optional to solve relative links
editor.setText( htmlStr, true );

The editor can be easily extended by save and load actions for instance. Please have a look at the sample classes for these use cases.

JavaHelp

If you want show the same help content in a Java application and on a website then it should look identical on both targets. If you use JavaHelp then you can add the follow code to the HelpSet:

<impl>
    <helpsetregistry helpbrokerclass="javax.help.DefaultHelpBroker" />
    <viewerregistry viewertype="text/html" viewerclass="com.inet.html.InetHtmlEditorKit" />
</impl>

The problem is the search index. The search index of JavaHelp also saves the positions of the text chunks. These positions can be a little different. That means we need to extend the original indexer:

package com.inet.indexer;
 
import com.sun.java.help.search.Indexer;
 
public class InetIndexer {
 
    public static void main( String[] args ) {
        Indexer.registerIndexerKitForContentType( "text/html", "com.inet.indexer.InetHtmlIndexerKit", null );
        Indexer.main( args );
    }
}
import ...
import com.sun.java.help.search.DefaultIndexerKit;
 
public class InetHtmlIndexerKit extends DefaultIndexerKit {
 
    @Override
    public Object clone() {
        return new InetHtmlIndexerKit();
    }
 
    @Override
    public String getContentType() {
        return "text/html";
    }
 
    @Override
    public void parse( Reader in, String file, boolean ignoreCharset, IndexBuilder builder, ConfigFile config ) throws IOException {
        InetHtmlDocument doc = new InetHtmlDocument();
        try {
            if( ignoreCharset ) {
                doc.putProperty( "IgnoreCharsetDirective", Boolean.valueOf( true ) );
            }
            doc.putProperty( Document.StreamDescriptionProperty, new URL( "file", null, file ) );
            doc.parse( in, 0 );
        } catch( BadLocationException e ) {
            e.printStackTrace();
        } catch( ChangedCharSetException cce ) {
            throw new com.sun.java.help.search.ChangedCharSetException( cce.getCharSetSpec(), cce.keyEqualsCharSet() );
        }
        try {
            String text = doc.getText( 0, doc.getLength() );
            this.builder = builder;
            this.config = config;
            this.file = file;
            documentStarted = false;
            super.parseIntoTokens( text, 0 );
            storeTitle( doc.getProperty( Document.TitleProperty ).toString() );
            endStoreDocument();
        } catch( BadLocationException e ) {
            e.printStackTrace();
        } catch( Exception e ) {
            e.printStackTrace();
        }
 
    }
 
    @Override
    public int parseIntoTokens( String arg0, int arg1 ) {
        return -1;
    }
 
}

Printing HTML

Like any browser, JWebEngine displays a page even if there are still some images pending. Any time an image has finished loading the page will be updated. In case the page should be printed to an image for instance, this feature has to be disabled by setting the media property. This forces the renderer to wait until all images have been loaded before it prints the page.

int width = ...;
int height = ...;
BufferedImage thumbnail = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB );
 
JTextPane textPane = new JTextPane();                                  // set connection timeout
textPane.setContentType( "text/html" )                                 // activate EditorKit
textPane.setSize( width, height );                                     // apply size to the renderer
textPane.setPage( new URL( "http://..." ) );                           // load the page
 
// set the media to print. This has to be done AFTER setPage() but BEFORE print()
textPane.getDocument().putProperty(InetHtmlDocument.PROPERTY_MEDIA, InetHtmlDocument.MEDIA_PRINT );
 
textPane.print( thumbnail.createGraphics() );                          // print thumnail to memory

Generating Thumbnails

This use case is very similar to the printing of HTML. But since thumbnail generation has to be fast, we set timeouts for the connection and the parser. This helps if the page is very large or if the connection cannot be established.

int width = ...;
int height = ...;
BufferedImage thumbnail = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB );
 
// We use the JTimeoutTextPane of JWebEngine to set a connection timeout
JTimeoutTextPane textPane = new JTimeoutTextPane( 3000 );
textPane.setContentType( "text/html" )                                 // activate EditorKit
((InetHtmlEditorKit)textPane.getEditorKit()).setParserTimeout( 2000 ); // set parser timeout
textPane.setSize( width, height );                                     // apply size to the renderer
textPane.setPage( new URL( "http://..." ) );                           // load the page
textPane.getDocument().putProperty(InetHtmlDocument.PROPERTY_MEDIA, InetHtmlDocument.MEDIA_PRINT );
textPane.print( thumbnail.createGraphics() );                          // print thumnail to memory
try {
   ImageIO.write( thumbnail, "png", new File( "out.png" ) );           // generate output file
} catch( IOException e ) {
   ...
}

If a small thumbnail is required it is recommended to first render to an larger image and scale down this image to the required size.

Browsing the Internet

If you want write an Internet browser, then the most work is to write a GUI. The JWebEngine part is very simple. First you need to register the component:

JEditorPane.registerEditorKitForContentType( "text/html", "com.inet.html.InetHtmlEditorKit" );

It is also helpful to extend JTextPane. In this class you can override some methods, for example you could set a browser-specific configuration in the editor kit like:

    @Override
    protected EditorKit createDefaultEditorKit() {
        InetHtmlEditorKit kit = new InetHtmlEditorKit( );
        kit.setDefaultConfig( InetHtmlConfiguration.getBrowserConfig() );
        return kit;
    }

Some web sites like Google show an error if the User Agent is unknown. To avoid this you need to set a well-known User-Agent. To do it you must copy a large part of the super class. This also makes it possible to receive the real URL after a redirect for a History. This can look like:

    /**
     * Override and copy the super implementation to set a User-Agent and add the URL to the History {@inheritDoc}
     */
    @Override
    protected InputStream getStream( URL page ) throws IOException {
        final URLConnection conn = page.openConnection();
        conn.setRequestProperty( "User-Agent", Configuration.USER_AGENT );
        if( conn instanceof HttpURLConnection ) {
            HttpURLConnection hconn = (HttpURLConnection)conn;
            hconn.setInstanceFollowRedirects( false );
            Object postData = getPostData();
            if( postData != null ) {
                handlePostData( hconn, postData );
            }
            int response = hconn.getResponseCode();
            boolean redirect = (response >= 300 && response <= 399);
 
            /*
             * In the case of a redirect, we want to actually change the URL
             * that was input to the new, redirected URL
             */
            if( redirect ) {
                String loc = conn.getHeaderField( "Location" );
                if( loc.startsWith( "http", 0 ) ) {
                    page = new URL( loc );
                } else {
                    page = new URL( page, loc );
                }
                return getStream( page );
            }
        }
 
        // Connection properties handler should be forced to run on EDT,
        // as it instantiates the EditorKit.
        if( SwingUtilities.isEventDispatchThread() ) {
            handleConnectionProperties( conn );
        } else {
            try {
                SwingUtilities.invokeAndWait( new Runnable() {
                    public void run() {
                        handleConnectionProperties( conn );
                    }
                } );
            } catch( InterruptedException e ) {
                throw new RuntimeException( e );
            } catch( InvocationTargetException e ) {
                throw new RuntimeException( e );
            }
        }
        history.add( page );
        return conn.getInputStream();
    }
 
    /**
     * Copy from super class
     * 
     * @return the property PostDataProperty
     */
    private Object getPostData() {
        return getDocument().getProperty( POST_DATA_PROPERTY );
    }
 
    /**
     * Copy from super class
     * 
     * @param conn
     *            current connection
     * @param postData
     *            current data
     * @throws IOException
     *             if an I/O error occurs.
     */
    private void handlePostData( HttpURLConnection conn, Object postData ) throws IOException {
        conn.setDoOutput( true );
        DataOutputStream os = null;
        try {
            conn.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
            os = new DataOutputStream( conn.getOutputStream() );
            os.writeBytes( (String)postData );
        } finally {
            if( os != null ) {
                os.close();
            }
        }
    }
 
    /**
     * Copy from super class
     * 
     * @param conn
     *            current connection
     */
    private void handleConnectionProperties( URLConnection conn ) {
        Document doc = getDocument();
        String type = conn.getContentType();
        if( type != null ) {
            setContentType( type );
            doc.putProperty( "content-type", type );
        }
        doc.putProperty( Document.StreamDescriptionProperty, conn.getURL() );
        String enc = conn.getContentEncoding();
        if( enc != null ) {
            doc.putProperty( "content-encoding", enc );
        }
    }

Converting HTML content

JWebEngine has several additional functions to convert between HTML and plain text

HtmlConverter.text2html( ... )        // convert plain text to HTML
HtmlConverter.html2text( ... )        // convert HTML to plain text

or to convert all referenced CSS styles to inlined styles for instance

HtmlConverter.html2inlinedHtml( ... )

which is useful if an page is included in another.

React on an asynchronous page load

Changing the content in the (optional) BaseEditor loads the HTML document in a separate thread. To get notified when this task is finished, the TextLoadListener can be used.

baseEditor.addTextLoadListener( new TextLoadListener(){
   public void notifyTextLoaded( Document doc ){
      // ... custom implementation
   }
});
 

© Copyright 1996 - 2017, i-net software; All Rights Reserved.