The Back Room
Charles

How to Smooth The Adobe Air HtmlLoader

byCharles on Jan.29, 2010, under Software

In a recent project we worked on; the client was very adamant about being able to display a plethora of different types of unstructured data inside of a kiosk.  This included information that contained tables and images inside of the same display at the same time.  The content also included a plethora of font variations.  The length of content was greatly variable and really didn’t have any defined or repeatable structure.

If you are familiar with the flash text fields htmlText capabilities, you will see that it can render basic html.  However, this just wasn’t enough for this project so we decided to implement the Adobe AIR HtmlLoader which; from my understanding runs an instance of webkit inside of AIR.  It’s awesome being able to load web content directly into a project; but there is one huge problem with the HtmlLoader.  You can’t smooth the HtmlLoader!  If you rotate or resize the loader, you will quickly find yourself with an ugly, poorly rendered mess.

After thinking about the issue for a while, I came up with a solution.  The trick is to load the web content inside of the HtmlLoader, then use the BitmapData, and Bitmap classes to render out a bitmap of the HtmlLoader.  You will then be able to apply smoothing, size, and rotate the newly created bitmap without much distortion.

When using this technique you lose the ability to scroll and navigate web content.  Our whole point was to not let people know there was a web page being displayed inside the kiosk; so this methodology worked very well for us.
Below I have provided the class that I use in order to implement the smooth html loader functionality.  Be careful because my class is using some external references.  Namely Tweener as well as GenericNotifyEvent.

package com.bostonproductions.web {

import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.html.HTMLLoader;
import flash.events.HTMLUncaughtScriptExceptionEvent;
import flash.geom.Matrix;

import com.bostonproductions.events.GenericNotifyEvent;
import com.caurina.transitions.Tweener;

public class smoothHtmlLoader extends MovieClip {

public static var HTMLREADY:String = “smoothHtmlLoader_HTMLREADY”;
private var theBrowser:HTMLLoader;
private var htmlToRender:String;
private var smoothCap:Sprite;
private var rawSmoothCap:Bitmap;
private var scrollPosition:int = 0;
private var tryCenter:Boolean = false;

private var targetHeight:Number = 650;
private var targetWidth:Number = 720;

// Alter the native height based on the resize ratio
private var HeightResizeRatio:Number = 1;

public function smoothHtmlLoader(_HeightResizeRatio:Number = 1, _targetWidth:Number = 0, _targetHeight:Number = 0):void {
HeightResizeRatio = _HeightResizeRatio;

if (_targetWidth != 0) {
targetWidth = _targetWidth;
}

if (_targetHeight != 0) {
targetHeight = _targetHeight;
}

createHtmlViewer();
}

/*
* DATE: 10/14/2009
* AUTHOR: Charles Palen
* NAME: displayHtml
* DESCRIPTION: Tries to fit all the html into the instance of
* the HTMLLoader.  If it cant fit it all, it MUST return the
* non-fitting html.
*/
public function displayHtml(_HtmlToDisplay:String):String {
var nonFittingHtml:String = “”;

htmlToRender = _HtmlToDisplay;

loadTargetLocation();

return nonFittingHtml;
}

/*
* DATE: 10/15/2009
* UPDATE: 10/28/2009
* The html parser now splits html up into chunks in instances where there are tables and images
* If this occurs – especially in the case of a table – we want to be able to CENTER it.
*
* Added optional boolean to specify we want to examine the content height, and resize/center the browser
* based on it!
*
* AUTHOR: Charles Palen
* NAME: setScrollPosition
* DESCRIPTION: Stores what should be the vertical scroll for this.  Once the html content loads it can be set.
*/
public function setScrollPosition(_VerticalScroll:int = 0, _TryCenterBrowser:Boolean = false):void {
scrollPosition = _VerticalScroll;
tryCenter = _TryCenterBrowser;
}

private function createHtmlViewer():void {
theBrowser = new HTMLLoader();
theBrowser.width = targetWidth;
//theBrowser.height = targetHeight;

theBrowser.placeLoadStringContentInApplicationSandbox = true;

theBrowser.addEventListener(Event.COMPLETE, htmlLoaded, false, 0, true);
theBrowser.addEventListener(HTMLUncaughtScriptExceptionEvent.UNCAUGHT_SCRIPT_EXCEPTION, scriptException, false, 0, true);
theBrowser.addEventListener(Event.LOCATION_CHANGE, htmlClicked, false, 0, true);

theBrowser.paintsDefaultBackground = false;
}

private function destroyHtmlViewer():void {
if (theBrowser != null) {
theBrowser.removeEventListener(Event.COMPLETE, htmlLoaded);
theBrowser.removeEventListener(HTMLUncaughtScriptExceptionEvent.UNCAUGHT_SCRIPT_EXCEPTION, scriptException);
theBrowser.removeEventListener(Event.LOCATION_CHANGE, htmlClicked);

theBrowser.cancelLoad();

if (contains(theBrowser) ) {
removeChild(theBrowser);
}

theBrowser = null;
}
}

private function loadTargetLocation():void {
if( theBrowser != null ) {
theBrowser.loadString(htmlToRender);

}
}

private function htmlLoaded(e:Event) {
// Remove the event
e.currentTarget.removeEventListener(Event.COMPLETE, htmlLoaded);

// Set the vertical scroll
theBrowser.scrollV = scrollPosition;
// If the try resize boolean is set – lets try it!
// Dont center – just shrink it if possible
if (tryCenter == true) {
var resizedHeight:Number = theBrowser.contentHeight;
var resizedWidth:Number = theBrowser.contentWidth;

if (resizedHeight > targetHeight) {
resizedHeight = targetHeight;
}
if (resizedWidth > targetWidth) {
resizedWidth = targetWidth;
}

theBrowser.width = resizedWidth;
theBrowser.height = resizedHeight;
targetWidth = resizedWidth;
targetHeight = resizedHeight;
}

theBrowser.width = targetWidth;
theBrowser.height = targetHeight * HeightResizeRatio;
//var bitmapAsset:Bitmap = e.target.content as Bitmap;
//bitmapAsset.smoothing = true;
Tweener.addTween(this, { alpha:1, time:.1, onComplete:generateSmoothVersion } );
}

private function generateSmoothVersion():void {
// Create the smoothed version
var tmpImage:BitmapData = new BitmapData(targetWidth, targetHeight, true,0×000000);
// Draw it – the last parameter set to true is SMOOTHING – tmpImage will be smoothed
var transformMatrix:Matrix = new Matrix();
//transformMatrix.translate(0, 500);
tmpImage.draw(theBrowser, null, null, null, null, true);
//tmpImage.draw(theBrowser,
// Create the bitmap
rawSmoothCap = new Bitmap(tmpImage);
rawSmoothCap.smoothing = true;
smoothCap = new Sprite();
smoothCap.addChild(rawSmoothCap);
//rawSmoothCap.x = 50;
//rawSmoothCap.y = 50;
addChild(smoothCap);
// Destroy the heavy html components to free up some memory
destroyHtmlViewer();
//swapChildren(theBrowser, smoothCap);
//theBrowser.alpha = 1;
//smoothCap.x = 300;
//smoothCap.y = 100;
// Broadcast so that we know its ready
if(tryCenter == true) {
dispatchEvent(new GenericNotifyEvent(smoothHtmlLoader.HTMLREADY, false, true, true));
}
}

private function destroySmoothVersion():void {
if (smoothCap != null) {
if (rawSmoothCap != null) {
if (smoothCap.contains(rawSmoothCap) ) {
smoothCap.removeChild(rawSmoothCap);
}

rawSmoothCap = null;
}

if ( contains(smoothCap) ) {
removeChild(smoothCap);
}

smoothCap = null;
}
}

private function htmlClicked(e:Event):void {
// Auto go back to wherever we were
//theBrowser.reload();
//loadTargetLocation();
}

private function scriptException(e:HTMLUncaughtScriptExceptionEvent):void {
trace(”ScriptException:” + e);
}

public function destroyInternals():void {
destroyHtmlViewer();
destroySmoothVersion();
}
}
}

:, , ,

4 Comments for this entry

  • szakou

    Thanks for this sharing.
    I’ve to develop a kiosk like application for a first time for a client. But i’ve doubt about what technologies to choose (Adobe AIR, Flash/Flex in Browser, HTML/Javascript in browser). The app will consisted of some dynamically html page and some images and/or swf object loaded sequentially and displayed during some amount of times.
    My question is that i don’t know if it’s possible to dynamically load an swf object, display it during some seconds and load another. Any help will be appreciated.
    Thanks in advance.

  • Charles

    Sorry szakou. I did not realize you had posted a question here.

    It is very possible to do what you are speaking of. The class you are looking for in order to load an external swf file is the “loader” class that is available in plain old flash.

    http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/display/Loader.html

    Using the loader to load external swf files could be a way that you could load several different applications into a single shell.

  • Oliver

    Hi, Great Post!
    I am developing an Adobe AIR application using Flex which renders HTML content!

    I have encoutered a problem, when the HTML application calls javascript window.open() functions the flex/AIR html component does not do anything. In order to make my AIR app create the required window creation, I have created a sub class of HTMLHost as per the documentation. This works fine, the application will
    create a new native window when the application calls a window.open function. But this does not meet the requirements of the project. Instead of creating a native window, I need the application to create a MDI
    floating window and render the html within that. I am using flexMDI and the floating panel framework. Is this something possible? I have been searching through forums and documentation but I cant not find a solution or workaround! I have even attempted to look through the flex framework source in an attempt to edit the HTMLLoader class in order to create a function to create MDI window – ‘HTMLoader.createMdiWindow()’ but the HTMLLoader class is located in flash.html.HTMLLoader which I cant seem to find in the flex SDK.

    Any help on this would be
    greatly appreciated!

  • Charles

    Hi Oliver,

    In my limited experience with the Flex framework I have never seen the ability to do what you are trying to do. I think you would have to manually manipulate several different canvas containers each with htmlLoaders in them.

    There are ways you can call flash functions from javascript code, so instead of calling window.open, you may have to trigger something else inside your air application that you then manually create a new canvas object with your new htmlLoader instance in it.

    Except for code executing inside the htmlLoader component I am pretty sure your javascript calls will be unable to affect anything in your higher level Flex/AIR application.

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...