ClipStation Clipboard Writer 2.0 Released

ClipStation is a free lightweight solution for writing to your user’s clipboard from an HTML page. Using a small SWF that is embedded dynamically via JavaScript, you can pass an unlimited number of content clips onto the clipboard.

ClipStation is designed to be lightweight, flexible, and easy to implement. What makes ClipStation different from other clipboard SWF solutions is the ability to decode HTML character entities, allowing you to pass complex HTML markup to the clipboard from within form elements, divs, pre tags, etc. We developed ClipStation for use on a widget sharing page we've implemented for a client. After looking around for a good lightweight cross-browser solution and coming up empty handed, we decided to build our own. We're now happy to offer it to you at the low, low price of free.

Version 2.0 includes changes to allow access to the clipboard in Flash Player 10. Adobe changed the security requirements for clipboard access in version 10 of the player; now a user action is required before a SWF may access the clipboard. Instead of using a single hidden instance of the ClipStation SWF, we embed an instance for every clip that the user clicks to perform the clipboard copy. A source distribution is available, so you can change the design to fit your needs.

More information and the release package can be found at thirdpartylabs.com/clipstation/

»Download ClipStation 2.0

, , ,

Automatic AMF class mapping in Zend (Part 2)

see Part 1

Polishing off automatic mapping of objects to the correct class from PHP (Zend) back to ActionScript turned out to be easier than I thought.

In addition to the more commonly used $_explicitType class property, Zend also supports using a function called getASClassName. You can either paste this into each of your remote class stubs, or modify the getClassName method in Serializer to do it automatically.

public function getASClassName() 
{
 return ">" . str_replace("_", ".", get_class($this)); 
}

Clearly you'll need to avoid underscores appearing package and class names. That may be true regardless if you're planning to take advantage of Zend's automatic class loading.

On the Flash side, if you're not using the Flex compiler, you can use this to automatically register class aliases to the correct string.

function autoRegisterClassAlias (localClass:Class):void
{
registerClassAlias(">" + flash.utils.describeType(localClass).@name.split("::").join("."), localClass)
}

I've made it compatible with the Flex version. If the greater-than character is the cause of annoyance and confusion, feel free to remove it, but you'll need to do so on the PHP classes as well.

I'm sure this won't be our last post on this topic. If you've got lots of experience with AMF and Zend (I honestly don't) please post your thoughts in the comments.

Automatic AMF class mapping in Zend (Part 1)

First off, much thanks to Lee Coltrane for figuring this out. He's a bit busy at the moment, so I'm writing this up without the benefit of his input which I'm sure will be extremely valuable once our schedules mesh up.

A few caveats right off the bat. 1) We're still figuring out our best practices. 2) This is Flex only (see below)  3) We haven't (yet) written any code to automatically populate the $_explicitType variable going back to Flash from PHP.

If you're reading this, you've more than likely come across a Flex metatag like the one below:

[RemoteClass(alias="com.domain.FooBar")]

Personally, I found it annoying to have to populate the alias manually. The compiler knows the package name and the class name. Don't make me type it again! And, in fact, the compiler will accept the metatag without it:

[RemoteClass]

The only problem is getting Zend (in our case) to map it to the correct remote class. The Adobe engineers saw fit to prepend a greater-than character to the outgoing classname when using this syntax. So, if you omit the alias, ">com.domain.FooBar" will be sent instead.

To get ZendAmf to map this automatically, you'll need to make a slight modification to the Deserializer.php class. Mine is located in Zend/Amf/Parse/Amf3/Deserializer.php (Note: I haven't even looked at the Amf0 class yet, but my understanding is that custom class mapping isn't supported in AMF0) Insert the following code around line 315 in the readObject method after it has tried to determine the className but before it has assigned a return object.

// Allow and map automatic aliases via RemoteClass metatag & flex
if (strpos ($className, '>') === 0)
{
  $className = substr ($className, 1);
}
//translation: If the classname begins with '>' use the rest of the string instead.

This addition will deal with incoming class mapping. If you want to remove the alias parameter from all of your ActionScript RemoteClass metatags, you'll need to format your $_explicitType entries like this:

$_explicitType = ">com.domain.FooBar";

...which is what we'll look at in Part 2! 

NOTE: I said at the very beginning this is Flex only. Compiling with the Flash IDE does not support the RemoteClass metaTag, so you'll need to use registerRemoteClass as you've probably read about elsewhere. Perhaps in part 2 (or 3?) I'll write up an ActionScript class that can register classes against the correct package names automatically. Until then, I'll give you a hint: flash.utils.describeType.

Passing Custom-Classed Parameters to AMF (PHP)

I spent a day or two pulling my hair out and wondering if AMFPHP should be considered abandon-ware (no). I could get data to go back-and-forth. That part was pretty easy. But I wanted to pass a custom configuration object to the server as part of my request and couldn't get any further than it showing up in PHP as an anonymous array.

I gave up, installed the Zend framework (which now includes an AMF implementation), and had similar (although slightly different) issues. After resorting to Charles to figure out what might be going wrong, I found the problem in my remote service class. It turns out my PHP chops were coming up short.

There are a bunch of AMF examples and tutorials out there, and I feel like I looked at most of them ; ) Very few talk about sending custom-classed parameters to the server. Instead, they show you how to pass numbers and strings (if anything at all). The best of these test-your-gateway examples have you send your parameter object to the server as an array. In php, you'd access those (array) properties like this: $config['myParam'].

However, if you've got mapping between Actionscript and PHP classes properly configured, that syntax isn't going to work. Actionscript lets you access an object's child properties via either 'dot syntax' or 'array syntax', but PHP is more picky.

Once you get your custom class mapping working, you need to access (class) properties like this: $config->myParam. Simple to be sure.

So, I thought I'd tried this (and every other syntax combination), but apparently not while I had the class mapping setup correctly. PHP errors in your AMF service class can be notoriously hard to debug. Most of the time when something went wrong with mine, I simply never got a response. But, once I fixed my service class, I could use either Zend or AMFPHP. Rock on.

You may remember my article on scrollRect and getBounds().

Colin Moock just posted a bitmap-based solution. Slower, but very, very effective.

This is foremost a note-to-self. That enticing entry in the ActionScript API for TextField events that looks like you ought to be able to detect changes to the text or htmlText property? That's not how it works. It's for TextFieldType.INPUT fields only. As the user makes changes, the event will fire, but for everything else, you're on your own. I keep managing to forget this and architecting things inappropriately as a result.

Mapping TextFormat Ranges

There are several reasons you might want to know where one TextFormat ends and another begins. If your application supports any HTML authoring, you'll likely need something like this, even if your HTML support is fairly basic. I've been working on a font loading management utility, so I needed something to detect which portions of the text use which font. Once I have this, I can use Font.hasGlyphs to determine whether I need to load a deeper subset of glyphs or can get away with a more minimal unicode range. (You can quit drooling. That code will be posted here when it's ready.)

Doing this requires two classes. The first determines whether two TextFormat instances are equal.

  1. package {
  2. import flash.text.TextFormat;
  3. public class TextFormatUtil {
  4. public static var defaultPropertySet:Array /* of String property names */ = ['font', 'size', 'color', 'underline', 'bold', 'italic', 'url'];
  5. public static var fontOnlyPropertySet:Array /* of String property names */ = ['font'];
  6. public static function equals (f1:TextFormat, f2:TextFormat, propertySet:Array=null)
  7. {
  8.   if (propertySet == null)
  9.   {
  10.     propertySet = defaultPropertySet
  11.   }
  12.   var match:Boolean = true
  13.   for (var i:String in propertySet)
  14.   {
  15.     var prop = propertySet[i]
  16.     //trace (i + " " + prop + " " + f1[prop] + " " + f2[prop])
  17.     if (f1[prop] != f2[prop])
  18.     {
  19.       match = false
  20.       break
  21.     }
  22.   }
  23.   return match
  24. }
  25. }
  26. }

Easy enough. Now, you could just walk through each character in the TextField one at a time testing for TextFormat equality. But that's really inefficient. Instead, I implemented a binary search.

  1. package {
  2. import flash.text.TextFormat;
  3. import flash.events.Event;
  4. import flash.text.TextField;
  5. public class TextFormatMapper {
  6. private var charsInTextFormatAtIndex : Array /* of int */;
  7. private var textField : TextField;
  8. public function TextFormatMapper() {
  9. }
  10. public function setTextField(tf:TextField)
  11. {
  12.   textField = tf
  13.   textField.addEventListener(Event.CHANGE, onTextFieldChanged)
  14. }
  15. protected function onTextFieldChanged (event:Event) {
  16.   mapTextFormat()
  17. }
  18. public function mapTextFormat()
  19. {
  20.   charsInTextFormatAtIndex = new Array()
  21.   var startIndex = 0
  22.   while (startIndex < textField.text.length)
  23.   {
  24.     var hi:int = textField.text.length
  25.     var lo:int = startIndex+1
  26.  
  27.     var format1:TextFormat = textField.getTextFormat(startIndex, startIndex+1)
  28.     while (lo < hi) {
  29.       var mid:int = Math.ceil (lo + ((hi - lo) / 2))
  30.       var format2:TextFormat = textField.getTextFormat(startIndex + 1, mid)
  31.       if ( TextFormatUtil.equals (format1,format2, TextFormatUtil.fontOnlyPropertySet) )
  32.       {
  33.         //try a larger span if possible
  34.         lo = mid //+ 1
  35.       }
  36.       else
  37.       {
  38.         //try a smaller span if possible
  39.         hi = mid - 1
  40.       }
  41.     }
  42.     var charsInTextFormat:int = hi - startIndex
  43.     charsInTextFormatAtIndex.push ( charsInTextFormat )
  44.     startIndex = hi
  45.   }
  46. }
  47. public function getCharsInTextFormatAtIndex() : Array {
  48.   return charsInTextFormatAtIndex;
  49. }
  50. }
  51. }

To use it, you create a new TextFormatMapper and pass your TextField to the setTextField method. You can see I'm automatically calling the mapTextFormat method when the Event.CHANGE fires on the TextField. For testing, I hardcoded it to only test against the font property, but you can see how to easily change this to another property set on line 44. Once it's finished, you can call getCharsInTextFormatAtIndex to get an array containing the length (in characters) of each distinct TextFormat (in this case, distinct font) encountered in the TextField.

This class will change a bit as I move forward with it, but I was really pleased to get this far.

mod_rewrite trouble

I'm going to keep my eyes open for an actual explanation for this behavior, but I thought I'd share a problem (and solution?) I ran into with mod_rewrite.

I was going about my business, doing the classic mapping of http://mydomain/subdir/var1/var2 to http://mydomain/subdir/index.php?id=var1&id=var2

I had two rules, one to handle a single variable, and another to handle two variables. The idea, of course, being to drill down to a deep link. It was working great for the single variable rule, as long as I didn't include a trailing slash. Even though my rewrite regular expression clearly stated that the trailing slash was optional. To taunt me even more, if I changed the rewrite to a redirect, it worked fine.

As it turned out, the page in question contains a frameset (I know, I know) and the frame source parameters were relative paths. Even though the page source URL had been re-written, the frame source URLs were attempting to load pages that weren't there. Changed the paths to absolute and everything works great.

Just something else to look at if you're having trouble with mod_rewrite, especially if some rules appear to work, and others don't. The frameset situation is going to be pretty rare, but the same problem might manifest with missing images, style-sheets, etc.

Smarty File Size Modifier

Here's a simple Smarty modifier that will format an integer that represents the number of bytes in a file as a human readable string.

Usage:
{$fileSizeInBytes|file_size}

Example:
{assign var=fileSizeInBytes value=10485760}
{$fileSizeInBytes|file_size}

{assign var=fileSizeInBytes value= 768000}
{$fileSizeInBytes|file_size}

{assign var=fileSizeInBytes value=303}
{$fileSizeInBytes|file_size}

Output:
10 MB
750 Kb
303 bytes

 
<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */
 
/**
 * Smarty file_size modifier plugin
 *
 * Type:     modifier<br>
 * Name:     file_size<br>
 * Purpose:  format file size represented in bytes into a human readable string<br>
 * Input:<br>
 *         - bytes: input bytes integer
 * @author   Rob Ruchte <rob at thirdpartylabs dot com>
 
 * @param integer
 * @return string
 */
function smarty_modifier_file_size($bytes=0)
{
    $mb = 1024*1024;
 
    if ($bytes > $mb)
    {
        $output = sprintf ("%01.2f",$bytes/$mb) . " MB";
    }
    elseif ( $bytes >= 1024 )
    {
        $output = sprintf ("%01.0f",$bytes/1024) . " Kb";
    }
    else
    {
        $output = $bytes . " bytes";
    }
 
    return $output;
}
 
/* vim: set expandtab: */
 
?>

,

Flash HTML Textfields – Kick it Old-School

Most professional-grade fonts don't register the bold & italic outlines with the same postscript name. For example, "Berthold Akzidenz Grotesk BE", "Berthold Akzidenz Grotesk BE Bold", "Berthold Akzidenz Grotesk BE Italic" So to get these to work, you've got to either use stylesheets or explicitly set the text to use the correct face.

I updated my portfolio recently, and I didn't have time to implement a new shell (unfortunately) or code the whole thing over (as much fun as that is). Nor did I feel like tracking down a bug that kept me from publishing the swf to target anything above Flash Player version 6. Yeah, 6.

There wasn't any StyleSheets class back then. Add to this my crazy text-layout-in-a-circle routine, and adding bold, italic, and link support is an absolute nightmare. But I did it, and now I can show you how.

1st, parse the html as xml and replace the <b> and <i> tags with <font face=""> expressions that will actually target the correct font. I actually wrote a class that lets you map any tag to any other tag.

Next, stick all the html in a temporary textfield and map out where all the TextFormat changes occur. I wrote another class that can compare two TextFormats for equivalence.

My text-in-a-circle code adds words one-at-a-time, effectively creating a custom word-wrap routine. However, I had trouble when I attempted to use a single TextField, so each row uses a separate one. If you're up to something like this, you have to keep track of how many characters you've already used in previous fields. At first, I was getting lots of off-by-one errors and found a carriage-return character (character code 13) kept slipping into my input. Funny, I didn't put that there...

Flash also doesn't do a great job rendering things like <b>word</b> <b>second word</b>. It likes to collapse that and the space disappears. I swapped the spaces for non-breaking-spaces (&nbsp;) Feels dirty, but gets the job done.

And remember all those posts about entity problems with HTML coming from XML? The big reason for this undertaking was so I could support links in my portfolio project descriptions. When I linked to google maps, guess what happened to the query parameter ampersands in the href ...

The code is so crufty, even I won't post it. Drop me a line and I'll send you a private peek.