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: */
 
?>

,