Jon Williams (jon)

Website: http://shovemedia.com


 

Posts by Jon Williams (jon):


ActionScript, XML, and Character Entities

See if you can make heads or tails out of this behavior. I couldn't.

var myXML:XML = <doc><foo>&mdash; &amp; &lt; &gt; &quot; &apos; &nbsp;</foo></doc>

trace (myXML) //                   &amp;mdash; &amp; &lt; &gt; " ' ᅠ
trace (myXML.toXMLString()) //     &amp;mdash; &amp; &lt; &gt; " ' ᅠ
trace (myXML.toString()) //        &amp;mdash; &amp; &lt; &gt; " ' ᅠ
trace ("--") //
trace (myXML.foo) //               &mdash; & < > " ' ᅠ
trace (myXML.foo.toXMLString()) // &amp;mdash; &amp; &lt; &gt; " ' ᅠ
trace (myXML.foo.toString()) //    &mdash; & < > " ' ᅠ

Why are single and double quotes (and nonbreaking spaces?!) DEcoded by toXMLString, but any entities (ampersands) Flash doesn't handle get (re)ENcoded?

What a nightmare! I'm no encoding expert (although I did drop some cheddar on O'Reilly's excellent "Fonts and Encodings," a 1000+ page sleep-aid I have had zero time to actually read), but is there a rationale here I can't see?

Still musing about what to do about it.

ActionScript API for the Longtail Player (aka JW)

Took me forever to figure out how to set Longtail Player content via ActionScript.

I tried some other stuff, but I couldn't get both the preview image and the flv video to work the way it would if you used the HTML-embed set-and-forget method.

//videoView is a reference to the JW Player instance on stage
videoView.config.image = img // path to img as String
videoView.config.file = flv // path to flv as String
videoView.sendEvent(ViewEvent.LOAD,videoView.config);

Flash and Loading External Fonts – Part 1

You may have heard this elsewhere and not believed it (I did ... didn't?). If you want your (loaded) fonts to work in (loaded) flash movies, those movies may not (bold because I mean it) refer to those fonts. Don'ts:

  • You may not put the field on stage, set it to the desired font, but (purposefully) turn off font embedding. Flash will assume this swf has no outlines for this font and refuse to look elsewhere.
  • You may not have a static textfield that points to a font you (also) wish to load dynamically. Flash will only embed those outlines used in the static text and refuse to look elsewhere.
  • You may not break these rules on your main timeline, in (exported) library symbols, or try to load fonts first, and then load a swf which breaks these rules. Fonts will be broken for those swfs that try.

There are other resources out there detailing a few techniques for embedding fonts via a loaded swf. I'm going to save my juice for talking about the joy that happens (doesn't happen) when you try to embed bold and italics (which works) and then use them (which probably won't).

Flash TextField <img> Part 2

I promised I'd show how to split html content into multiple textfields to get around the fact that Flash's TextFields go nuts when you actually try to use the <img> tag. I do try to keep my promises. This post -- QED.

var inlineHtml:String = "";
 
var nodes = myXML.description.children()
for each(var node in nodes)
{
	var html:String = getHTMLContent("<doc>"+node.toXMLString()+"</doc>")
 
	if (html != "")
	{
		//Is this a block level tag: P, OL, etc...
		if ( isBlockTag (html, ["P", "OL", "UL"]) )
		{
		  if (inlineHtml != "")
		  {
		  	addSubfield(inlineHtml, stylesAsset.style)
		  	inlineHtml = ""
		  }
		  addSubfield(html, stylesAsset.style)
		}
		else
		{
		  inlineHtml += html + " " //NOTE: is a final space a major problem even with collapseWhite?
		}
	}
}
 
//anything left over as non-block content?
if (inlineHtml != "")
{
  addSubfield(inlineHtml, stylesAsset.style)
  inlineHtml = ""
}		
 
//
//
//
 
private function isBlockTag(html:String, blockTags:Array) :Boolean
{
	//convert the html string to XML
	var xml:XML = new XML(html)
 
	var nodeName = xml.name()
 
	if (nodeName != null)
	{
		for (var i in blockTags)
		{
			if (blockTags[i].toLowerCase() == nodeName.localName.toLowerCase())
			{
				return true
			}
		}
	}
	return false
}

The first block assumes we've got an XML snippet with a description node. We're going to walk through all the children of that node, and if it's a block-level element like <p> or <ul> we'll pass it (along with a stylesheet) to a function called addSubfield which will create the TextField object, set the StyleSheet, and set the HTML (this, and performing the actual layout of stacking one field atop the other is left as an exercise for the reader).

I showed you the getHTMLContent function in a previous article. Wrapping the argument in <doc> tags ensures we get all the content regardless of whether the content is a single text node, multiple HTML nodes, or mixed-content. I've been doing this a lot in a few projects -- works great -- so far.

Skinning the Longtail Player (aka JW)

This site is quickly becoming (just as we planned it ... Mu-hu-ha-ha!) a repository documenting things that took ages to figure out, we know we'll need again, but not often enough to trust our (rapidly aging) memories. The fact that its public and you get to play along is a bonus. I say that because writing this stuff up takes time, especially to do so clearly, neatly, and accurately. I have four projects going on, but I'm taking a moment to save this before I loose it; though perhaps muddled, disheveled, and ... suspicious.

Documentation for the Jeroen Wijering video player (http://www.longtailvideo.com/) leaves something to be desired -- it's great if you want to use it as they describe. Either:
1) embed a video in an HTML page where the Flash document is (and nothing but) the video player.

2) embed a player (and perhaps its skin) by loading those components at runtime.

These are great approaches. What I'd like to do is embed my player and skin at compile time causing it to bake directly into the swf I'm distributing. As a bonus, I'd like to compile against swc files in both cases so that:

a) Compiles are faster.

b) There is one-and-only-one version of the ActionScript floating around my harddrive which is messy enough already thank you.

I know I can accomplish "b" without a swc by adding the jw project folder to my classpath, but then that's one more thing to grab a copy of when I'm sending source to clients. Which I will forget about, and they won't actually try compiling anything until six months later at which point my copy of the source has changed ... See? I need a swc and I have good reasons.

Step 1: Create the player.swc

Open the player.fla that comes with the jw source. In the publish settings, check off create swc. Compile. Grab player.swc and add it to the classpath in your build tool of choice.

Step 2: The skin. (I'll post mine here later, but you should understand the steps)

Make a new fla, grab a copy of the library from the original and drop it in the new Skin.fla. Edit the properties of the "player" symbol. Set it to export with a class that we'll use in a bit CustomPlayerSkin. Follow the previous instructions for exporting a swc and adding it to your classpath.

In your project, you'll want to do something like this:


package {
	import com.jeroenwijering.events.ModelEvent;
	import com.jeroenwijering.events.ViewEvent;
	import com.jeroenwijering.player.View;

	import flash.events.Event;

	import com.jeroenwijering.events.PlayerEvent;
	import com.jeroenwijering.player.Player;

	import flash.display.MovieClip;

	/**
	 * @author projects
	 */
	public class PlayerHarness extends MovieClip {
		private var jwPlayer : Player;
		private var skin : CustomPlayerSkin;

		public function PlayerHarness()
		{
			addEventListener (Event.ADDED_TO_STAGE, onAdded)
		}

		private function onAdded(event : Event) : void
		{
		  populateAssets()
		}

		private function populateAssets(e:Event=null)
		{
			//jwPlayer
			jwPlayer = new Player()
			//remove default skin
			jwPlayer.removeChild (jwPlayer.player)
			//skin
			skin = new CustomPlayerSkin()
			jwPlayer.addChild(skin)

			//set new skin
			jwPlayer.skin = skin
			jwPlayer.width = skin.width
			jwPlayer.height = skin.height

			jwPlayer.config.autostart = true
			jwPlayer.config.fullscreen = true
			jwPlayer.config.resizing = false

			//size and position
			jwPlayer.config.width = this.stage.stageWidth
			jwPlayer.config.height = this.stage.stageHeight
			//skin.x = 0
			//skin.y = 0

			jwPlayer.addEventListener(PlayerEvent.READY, playerReady);

			addChild(jwPlayer)
		}

		private function playerReady(evt:Event=null) {
			var view:View = evt.target.view;

			//LOAD AN FLV!
			view.sendEvent(ViewEvent.LOAD, {file:"phone.flv"});

			//ADD AN EVENT
			view.addModelListener(ModelEvent.TIME, onPlayerTimeUpdate)
		}

		private function onPlayerTimeUpdate (e:ModelEvent)
		{
			trace ("pos" + e.data.position)
		}
	}
}

Photoshop Action – Save Slice for Web

Rob noticed today that every time he re-opens his site design doc the save-for-web window forgets that he's set it to "single slice" and not "all slices." We fumbled for a solution for a bit before settling on this: an action that copies the current slice to a fresh document and then invokes save-for-web from there.

This works because, as long as there's no current selection (make sure there's no current selection), the copy command will copy the content of the currently selected slice. Nice, eh?

Save the downloaded file as /Users/YOURUSER/Library/Application Support/Adobe/Adobe Photoshop CS4/Presets/Actions/shovemedia Actions.atn

Open Photoshop, go to the Actions pallete, click the menu in the upper right corner and select Load Actions...

I made this on (a Mac) CS4 -- your mileage may vary. Shouldn't matter though -- it's at least as easy to just record the action yourself.

Photoshop Tip – Transform Selection

I'm not sure how I missed this -- it's been in Photoshop since at least CS2 and from some of my Google results, probably far earlier than that.

When we cut images for the web, we use the Marquee tool a lot, but it can be a real pain to hit your corners exactly on the first try -- sometimes the pixels you're using as a visual guide are off screen if you've zoomed in far enough to see what you're doing! The solution (of course) is Transform Selection which gives you scale, rotate, and skew control over your current selection just like Transform Layer without messing with the underlying pixels. Very handy for expanding and trimming those rectangular selections.

Access it via right-click (command-click on Mac) of the selection or the Selection menu.

Flash TextField <img>

This wasn't the first time I struggled to incorporate <img> tag support into a project, but it might be the last. The bugs with this "feature" are so bad, that for all but the simplest of cases (and some of those), it's rendered completely useless. Today, I worked around another problem with links exhibiting wild layout schizophrenia. This time, it would appear to be a problem with the combination of a:hover styles and <img> tags.

Now, I've held back talking trash on the TextField because of all the version 10 metric goodness I'd heard we were getting. Finally the TextField has gotten some much needed attention. Decent support for multiple languages going different directions in the same document. Solid, hard stuff. So surely Adobe fixed all (some of?) those bugs that have been around for, what, 2, 3, maybe even 4 versions (god I'm getting old)? ... No.

Can we maybe have html support that doesn't suck, with stylesheet support that doesn't suck? Together? At the same time? I'll give you a pass on font embedding even though that's been ... superbly difficult even longer because I understand the pressure from the foundaries.

In our next episode, I'll show you how to carve up your htmlText into multiple TextFields so your links stay where you put them (I tried everything else). Because what else are you going to do but hack around it?

Load HTML from XML – Part II

Update: had to take another look at this due to Flash busting my character entities!

In the last installment, I showed you a function that will walk through an XML node's (multi-part) children and return an HTML string. This approach is unfortunately flawed -- whitespace collapsing is a bit over-eager resulting in XML such as the following:
This is a <a href="page.html">link</a>

converting to:
This is alink
(Note the missing space.)

Fortunately, I have not only a solution, but since it uses regex, it ought to be a good bit more efficient:

package {

	import StringUtils;
	import CharacterEntity;

	public class XmlUtil {

		static public function getHTMLContent (xml:*):String {
			//trace (typeof(xml) + "   " + xml.toXMLString())

			if (typeof (xml) == 'string') xml = new XML(xml)

			var html = ""
			var prettyPrint = XML.prettyPrinting
			XML.prettyPrinting = false
			var ignoreWhite = XML.ignoreWhitespace
			XML.ignoreWhitespace = false

			var children = xml.children()
			var len = children.length()
			if (len)
			{
				//trace ('Multiple Children')
				for ( var i=0; i<len; i++ )
				{
					var decoded = CharacterEntity.decodeXHTML(children[i].toXMLString() , true)
					html += decoded
				}
				html = StringUtils.removeExtraWhitespace( html )

			}
			else
			{
				//trace ('Simple Content')
				var str = StringUtils.removeExtraWhitespace( CharacterEntity.decodeXHTML(xml.toXMLString(), true) )
				html += str
			}

			XML.prettyPrinting = prettyPrint
			XML.ignoreWhitespace = ignoreWhite

			//logger.info ("HTML " + escape(html))

			return html
		}
	}
}

You'll need two libraries: StringUtils from the worship-worthy studio of Grant Skinner CharacterEntity, originally written for AS2 by Jim Cheng and kindly converted to AS3 by Thirdparty Labs.

The code is a lot simpler now, but for completeness, I'll give you a quick run-down. If you pass in a String (accessing an attribute or text node could actually cause this), we convert it to XML first. First, we turn ignoreWhitespace off since it's the source of the issue above. Walk through the children (if they exist) decoding the entities and remove any additional whitespace. The "true" parameter on the decodeXHTML method is explained in this post.

Load HTML from XML source – Part I

UPDATE:
I managed to forget TextField.condenseWhite = true!

Now, although that will handle 90% of your XML -> HTML whitespace issues. There are a few scenarios where the hints in this series come in handy:

  • You want more control over XML -> HTML tag handling. For instance you want to avoid an extraneous wrapping tag.
  • The CharacterEntity class is still relevant and useful.
  • You're using TextField.styleSheet. If you're not using a stylesheet, you can set the htmlText and then access the html with collapsed whitespace by reading the htmlText property back. If you do use a stylesheet, sniffing TextField.htmlText won't show collapsed whitespace).
  • We often, pull our dynamic content into Flash via XML. A Lot. As in pretty much exclusively. Sometimes, we'll have honest-to-goodness information architecture to carve up that data semantically. We end up with a structured tree of simple text nodes. The XML parsing routine figures out how to turn this into a view. Great for all kinds of reasons. But sometimes, we just want to treat a node as a block of HTML and pass it into the htmlText property of a TextField.

    This is unnecessarily hard. Neither XML.toString() nor XML.toXMLString() do what you want for this case. But the following function will:

    private function getHTMLContent (xmlString:String):String
    {
      var html = ""
      var prettyPrint = XML.prettyPrinting
      XML.prettyPrinting = false
      var ignoreWhite = XML.ignoreWhitespace
      XML.ignoreWhitespace = true

      var xml:XML = new XML (xmlString)
      var children = xml.children()

      if (children.length())
      {
        for each (var i in children)
        {
          html += i.toXMLString()
        }
      }
      else
      {
        html += xml
      }

      XML.prettyPrinting = prettyPrint
      XML.ignoreWhitespace = ignoreWhite

      return html
    }

    To use it, pass the XML node containing the HTML, and the function will return the appropriate HTML String. Let's say your XML looks like:

    <services>
    <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br /><br />Aliquam in lectus quis nisl lacinia dignissim.
    <ul>
      <li>Proin viverra.</li>
      <li>Phasellus tristique.</li>
    </ul>
    </description>
    </services>

    Then, after loading the XML document, you'd do something like:

    var desc = getHTMLContent(myXML.description)
    content_tf.condenseWhite = true
    content_tf.htmlText = desc

    If you skip this step, you'll end up including the description node (which isn't what you want) or mangling the mixed-content nature (omitting the br tags or the parent-less "Lorem ipsum" opening sentence or something equally as wrong). The script should also works if your content is a straight-forward text node.

    The prettyPrint and ignoreWhitespace stuff keeps your whitespace from wreaking havok with Flash's HTML format (which is different from a browser in that it'll happily add extra newlines when they appear in the source).

    Next time, I'll incorporate Rob's entity decoding script since Flash's built-in entity support is pretty lacking.