Index: sifr.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/sifr/sifr.css,v
retrieving revision 1.1
diff -u -p -r1.1 sifr.css
--- sifr.css	21 Feb 2006 21:11:36 -0000	1.1
+++ sifr.css	18 Aug 2007 16:09:19 -0000
@@ -5,4 +5,17 @@
 .sifr-font {
   font-size: 2em;
   vertical-align: middle;
-}
\ No newline at end of file
+}
+
+.sifr-font-color {
+  float: left;
+  width: 14px;
+  height: 14px;
+  margin: 0 3px 0 0;
+  border: 1px solid black;
+}
+
+.sifr-rule-op img {
+  margin: 0 6px 0 0;
+}
+
Index: sifr.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/sifr/sifr.install,v
retrieving revision 1.5
diff -u -p -r1.5 sifr.install
--- sifr.install	30 Jan 2007 21:55:05 -0000	1.5
+++ sifr.install	19 Aug 2007 15:29:47 -0000
@@ -8,24 +8,11 @@ function sifr_install(){
     case 'mysqli':
       db_query("CREATE TABLE {sifr} (
           rid int(10) NOT NULL default '0',
+          plugin VARCHAR(50) NOT NULL default '',
           name varchar(50) NOT NULL default '',
-          font varchar(100) NOT NULL default '',
           selector varchar(255) NOT NULL default '',
-          color varchar(10) NOT NULL default '',
-          linkcolor varchar(10) NOT NULL default '',
-          hovercolor varchar(10) NOT NULL default '',
-          bgcolor varchar(10) NOT NULL default '',
-          transparent tinyint(3) NOT NULL default '0',
+          properties longtext NOT NULL default '',
           weight float NOT NULL default '0',
-          letterspacing varchar(10) NOT NULL default '',
-          fontsize varchar(10) NOT NULL default '',
-          paddingtop varchar(10) NOT NULL default '',
-          paddingright varchar(10) NOT NULL default '',
-          paddingbottom varchar(10) NOT NULL default '',
-          paddingleft varchar(10) NOT NULL default '',
-          textalign varchar(10) NOT NULL default '',
-          underline tinyint(3) NOT NULL default '0',
-          lettercase varchar(10) NOT NULL default '',
           UNIQUE KEY rid (rid),
           KEY rid_2 (rid)
         ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
@@ -71,3 +58,49 @@ function sifr_update_2() {
   }
   return $ret;
 }
+
+/**
+ * Add plugin name to rules table.
+ */
+function sifr_update_3() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {sifr} ADD plugin VARCHAR(50) NOT NULL default '' AFTER rid");
+      $ret[] = update_sql("UPDATE {sifr} SET plugin = 'sifr'");
+      break;
+
+    case 'pgsql':
+      break;
+  }
+  return $ret;
+}
+
+/**
+ * Add column properties and serialize existing sIFR properties.
+ */
+function sifr_update_4() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {sifr} ADD properties longtext NOT NULL default '' AFTER selector");
+      $columns = array('font', 'color', 'linkcolor', 'hovercolor', 'bgcolor', 'transparent', 'letterspacing', 'fontsize', 'paddingtop', 'paddingright', 'paddingbottom', 'paddingleft', 'textalign', 'lettercase', 'underline');
+      $result = db_query("SELECT rid, ". implode(', ', $columns) ." FROM {sifr}");
+      while ($rule = db_fetch_array($result)) {
+        $rid = array_shift($rule);
+        $properties = serialize($rule);
+        $ret[] = update_sql("UPDATE {sifr} SET properties = '$properties' WHERE rid = $rid");
+      }
+      foreach ($columns as $column) {
+        $ret[] = update_sql("ALTER TABLE {sifr} DROP COLUMN $column");
+      }
+      break;
+
+    case 'pgsql':
+      break;
+  }
+  return $ret;
+}
+
Index: sifr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/sifr/sifr.module,v
retrieving revision 1.18
diff -u -p -r1.18 sifr.module
--- sifr.module	11 Aug 2007 00:33:20 -0000	1.18
+++ sifr.module	20 Aug 2007 01:48:56 -0000
@@ -7,8 +7,11 @@
  * sIFR Module for Drupal
  * by Jeff Robbins - www.lullabot.com
  * development sponsored by Bryght
- * Implements sIFR capabilities for all pages.
- * http://www.mikeindustries.com/sifr/
+ * Implements text replacement capabilities for all pages.
+ * 
+ * @todo Implement generic download handler for all generated files.
+ * @todo Allow different rules for each site.
+ * @todo Rename module to textreplacement or the like.
  */
 
 /**
@@ -16,20 +19,19 @@
  */
 function sifr_menu($may_cache) {
   $items = array();
+  $access = user_access('administer site configuration');
   if ($may_cache) {
     $items[] = array(
-      'title' => t('sIFR'),
+      'title' => t('Text replacement'),
       'path' => 'admin/settings/sifr',
       'description' => t('Configure which HTML text elements in your theme are rendered with Flash-based fonts.'),
-      // best way?
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'callback' => 'sifr_rules',
     );
     $items[] = array(
       'title' => t('Rules'),
       'path' => 'admin/settings/sifr/rules',
-      // best way?
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'callback' => 'sifr_rules',
       'type' => MENU_DEFAULT_LOCAL_TASK,
       'weight' => -2,
@@ -37,25 +39,16 @@ function sifr_menu($may_cache) {
     $items[] = array(
       'title' => t('Add rule'),
       'path' => 'admin/settings/sifr/addrule',
-      // best way?
-      'access' => user_access('administer site configuration'),
-      'callback' => 'sifr_edit_rule',
+      'access' => $access,
+      'callback' => 'drupal_get_form',
+      'callback arguments' => 'sifr_add_rule',
       'type' => MENU_LOCAL_TASK,
       'weight' => -1,
     );
     $items[] = array(
-      'title' => t('Delete rule'),
-      'path' => 'admin/settings/sifr/rules/delete',
-      // best way?
-      'access' => user_access('administer site configuration'),
-      'callback' => 'sifr_delete_confirm',
-      'type' => MENU_CALLBACK,
-    );
-    $items[] = array(
       'title' => t('Manage fonts'),
       'path' => 'admin/settings/sifr/manage',
-      // best way?
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'callback' => 'sifr_manage',
       'type' => MENU_LOCAL_TASK,
       'weight' => 2,
@@ -68,26 +61,47 @@ function sifr_menu($may_cache) {
       'type' => MENU_CALLBACK,
     );
     $items[] = array(
-      'title' => t('Download sifr file'),
+      'title' => t('Download font file'),
       'path' => 'sifr/download',
       'callback' => 'sifr_octet_download',
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'type' => MENU_CALLBACK,
     );
   }
   else {
     $items[] = array(
+      'path' => 'admin/settings/sifr/regenerate',
+      'access' => $access,
+      'callback' => 'sifr_regenerate_rules',
+      'type' => MENU_CALLBACK,
+    );
+    $items[] = array(
       'title' => t('Edit'),
       'path' => 'admin/settings/sifr/edit',
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'callback' => 'sifr_edit_rule',
       'type' => strpos($_GET['q'], 'admin/settings/sifr/edit') === 0 ? MENU_LOCAL_TASK : MENU_CALLBACK,
       'weight' => 5,
     );
     $items[] = array(
+      'path' => 'admin/settings/sifr/duplicate',
+      'access' => $access,
+      'callback' => 'sifr_duplicate_rule',
+      'type' => MENU_CALLBACK,
+      'weight' => 5,
+    );
+    $items[] = array(
+      'title' => t('Delete rule'),
+      'path' => 'admin/settings/sifr/rules/delete',
+      'access' => $access,
+      'callback' => 'drupal_get_form',
+      'callback arguments' => 'sifr_delete_rule_form',
+      'type' => MENU_CALLBACK,
+    );
+    $items[] = array(
       'title' => t('Delete font'),
       'path' => 'admin/settings/sifr/deletefont',
-      'access' => user_access('administer site configuration'),
+      'access' => $access,
       'callback' => 'sifr_fontdelete_confirm',
       'type' => strpos($_GET['q'], 'admin/settings/sifr/deletefont') === 0 ? MENU_LOCAL_TASK : MENU_CALLBACK,
       'weight' => 6,
@@ -99,50 +113,118 @@ function sifr_menu($may_cache) {
 }
 
 /**
+ * Retrieve available plugins.
+ */
+function sifr_plugins() {
+  static $listing;
+  
+  if (isset($listing)) {
+    return $listing;
+  }
+  
+  $dir = drupal_get_path('module', 'sifr') .'/plugins';
+  $listing = file_scan_directory($dir, '.+\.inc', array('.', '..', 'CVS', '.svn'), 0, FALSE, 'name');
+  foreach ($listing as $plugin) {
+    include_once($plugin->filename);
+    $function = $plugin->name .'_sifr_plugin';
+    if (function_exists($function)) {
+      $listing[$plugin->name] = $function();
+    }
+    else {
+      // Prevent use of malformed plugins.
+      unset($listing[$plugin->name]);
+    }
+  }
+  
+  return $listing;
+}
+
+/**
  * List the rules
  */
 function sifr_rules() {
+  $plugins = sifr_plugins();
   $rules   = sifr_get_rules();
-  $mypath  = drupal_get_path('module', 'sifr');
-  $header  = array(t('Rule Name'), t('CSS Selector'), t('Font'), t('Colors'), '', '');
-  $editimg = theme('image', $mypath .'/images/editor.png');
-  $delimg  = theme('image', $mypath .'/images/delete.png');
+  $sifrdir = drupal_get_path('module', 'sifr');
+  $header  = array(t('Plugin'), t('Rule Name'), t('CSS Selector'), t('Font'), t('Colors'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2));
+  $rows    = array();
+  $img_edit      = theme('image', $sifrdir .'/images/editor.png');
+  $img_duplicate = theme('image', $sifrdir .'/images/copy.png');
+  $img_delete    = theme('image', $sifrdir .'/images/delete.png');
   foreach ($rules as $r) {
-    $fontroot = substr($r->font, strrpos($r->font, '/') + 1);
+    $function = $r['plugin'] .'_sifr_rules';
+    if (function_exists($function)) {
+      $fontstyle = $function($r);
+    }
+    $color_names = array(
+      'color' => t('Font color'),
+      'linkcolor' => t('Link color'),
+      'hovervolor' => t('Hover color'),
+      'bgcolor' => t('Background color'),
+    );
+    $colors = '';
+    foreach ($fontstyle['colors'] as $key => $color) {
+      $colors .= '<div class="sifr-font-color" style="background-color: '. $color .';" title="'. $color_names[$key] .'"> </div>';
+    }
+    $operations = array(
+      l($img_edit, 'admin/settings/sifr/edit/'. $r['rid'], array('title' => t('Edit'), 'class' => 'sifr-rule-op'), NULL, NULL, FALSE, TRUE),
+      l($img_duplicate, 'admin/settings/sifr/duplicate/'. $r['rid'], array('title' => t('Duplicate'), 'class' => 'sifr-rule-op'), NULL, NULL, FALSE, TRUE),
+      l($img_delete, 'admin/settings/sifr/rules/delete/'. $r['rid'], array('title' => t('Delete'), 'class' => 'sifr-rule-op'), NULL, NULL, FALSE, TRUE),
+    );
     $rows[] = array(
-      $r->name, $r->selector,
-      $fontroot,
-      "<div style='width:90px; height:16px'>
-      <div style='float:left; width:16px; height:16px; background-color:$r->color; margin: 0 3px 0 0' title='". t('font color') ."'> </div>
-      <div style='float:left; width:16px; height:16px; background-color:$r->linkcolor; margin: 0 3px 0 0' title='". t('link color') ."'> </div>
-      <div style='float:left; width:16px; height:16px; background-color:$r->hovercolor; margin: 0 3px 0 0' title='". t('hover color') ."'> </div>
-      <div style='float:left; width:16px; height:16px; background-color:$r->bgcolor; margin: 0 3px 0 0' title='". t('background color') ."'> </div>
-      </div>
-      ",
-      l($editimg .' '. t('edit'), 'admin/settings/sifr/edit/'. $r->rid, array(), NULL, NULL, FALSE, TRUE),
-      l($delimg .' '. t('delete'), 'admin/settings/sifr/rules/delete/'. $r->rid, array(), NULL, NULL, FALSE, TRUE));
+      $plugins[$r['plugin']]['title'],
+      $r['name'],
+      $r['selector'],
+      $fontstyle['font'],
+      $colors,
+      $r['weight'],
+      implode('', $operations),
+    );
   }
-  $output .= theme_table($header, $rows, array('style' => 'width:100%; clear:both;'));
+  $output .= theme_table($header, $rows, array('style' => 'width: 100%; clear: both;'));
   $output .= '<p>';
-  $output .= l(t('Add another rule'), 'admin/settings/sifr/addrule') .'</p>';
-  $output .= l(theme('image', $mypath .'/images/sifr.png', 'sIFR', 'Link to the sIFR', array()), 'http://www.mikeindustries.com/sifr/', array(), NULL, NULL, FALSE, TRUE);
+  $output .= l(t('Add another rule'), 'admin/settings/sifr/addrule');
+  $output .= ' | ';
+  $output .= l(t('Regenerate rules'), 'admin/settings/sifr/regenerate');
+  $output .= '</p>';
+  
+  // Display logos of supported plugins.
+  $output .= '<h4>'. t('Supported plugins') .'</h4>';
+  foreach ($plugins as $plugin) {
+    $output .= l(theme('image', $sifrdir .'/plugins/'. $plugin['name'] .'.png', $plugin['title'], 'Visit homepage of '. $plugin['title'], array()), $plugin['url'], array(), NULL, NULL, FALSE, TRUE);
+  }
+  
+  drupal_add_css(drupal_get_path('module', 'sifr') .'/sifr.css', 'module', 'all', FALSE);
   
   return $output;
 }
 
 /**
- * Manage the .swf files
+ * Regenerate all JavaScript and Stylesheet files for all plugins and rules.
+ */
+function sifr_regenerate_rules() {
+  $active_plugins = variable_get('sifr_plugins', array());
+  foreach ($active_plugins as $plugin) {
+    sifr_render_rules_js($plugin);
+    sifr_css_screen($plugin, FALSE);
+  }
+  
+  drupal_goto('admin/settings/sifr');
+}
+
+/**
+ * Manage font files.
  */
 function sifr_manage() {
   $sifr = str_replace(' ', "%20", sifr_find_sifr());
   
-  drupal_set_title(t('Manage sIFR fonts'));
+  drupal_set_title(t('Manage text replacement fonts'));
   
   $delimg  = theme('image', drupal_get_path('module', 'sifr') .'/images/delete.png');
   $filedir = file_directory_path() .'/sifr';
   
   $header  = array(t('Font'), t('Location'), '');
-  foreach (sifr_get_swfs() as $file => $name) {
+  foreach (sifr_get_fonts() as $file => $name) {
     $editable = strpos($file, $filedir) === FALSE ? FALSE : TRUE;
     $rows[] = array(
       $name,
@@ -159,42 +241,30 @@ function sifr_manage() {
 function sifr_upload() {
   $form['#attributes'] = array('enctype' => 'multipart/form-data');
   
-  $instructions = t('
-  <h3>Good places to find sIFR fonts:</h3>
-  <ul>
-    <li><a href="http://sifrfonts.com/">SIFRfonts.com</a></li>
-    <li><a href="http://www.fontsmack.com/">Font Smack</a></li>
-    <li><a href="http://www.isarie.com/?p=17">Stefan Isarie</a></li>
-  </ul>
-  <p>Just download \'em from there and upload \'em here.</p>
-  
-  <h3>To create your own sIFR font files:</h3>
-    <ol>
-      <li>Download these files to the same directory: !files</li>
-      <li>Open the file in Macromedia Flash.</li>
-      <li>Double-click the invisible textbox in the middle of the stage. If the &quot;Properties&quot; palette is not already visible, open it by selecting &quot;Window > Properties&quot;, and select which font you\'d like to use from the drop down menu. If you select a TrueType font, you can also create bold and italic styles for your font by clicking on the &quot;I&quot; or &quot;B&quot; buttons. The standard sifr.fla file contains most of the English characters you will generally need. If you need to embed additional characters or languages, click the &quot;Character&quot; button and select more characters from there.</li>
-      <li>To export the new file, choose &quot;File > Export&quot; and save as fontname.swf</li>
-      <li>Upload the your font file below.</li>
-    </ol>',
-    array(
-      '!files' => l('sifr.fla', 'sifr/download/sifr.fla') .', '. l('customize_me.as', 'sifr/download/customize_me.as') .', '. l('dont_customize_me.as', 'sifr/download/dont_customize_me.as')
-      )
-  );
+  // Display instructions for each plugin.
   $form['instructions'] = array(
-    '#type' => 'fieldset',
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#title' => t('Instructions'),
-    '#attributes' => array('style' => 'margin-top: 15px'),
-  );
-  $form['instructions']['instructions'] = array(
-    '#type' => 'markup',
-    '#value' => $instructions,
+    '#value' => '<p />',
   );
+  $plugins = sifr_plugins();
+  foreach ($plugins as $plugin) {
+    $function = $plugin['name'] .'_sifr_plugin_instructions';
+    if (function_exists($function)) {
+      $form['instructions'][$plugin['name']] = array(
+        '#type' => 'fieldset',
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#title' => t('@plugin instructions', array('@plugin' => $plugin['title'])),
+      );
+      $form['instructions'][$plugin['name']]['instructions'] = array(
+        '#type' => 'markup',
+        '#value' => $function(),
+      );
+    }
+  }
   $form['sifrfile'] = array(
     '#type' => 'file',
-    '#title' => t('Upload sIFR font file'),
-    '#description' => t('Select the .swf file that you created using the instructions above.'),
+    '#title' => t('Upload font file'),
+    '#description' => t('Select the font file that you created using the instructions above.'),
     '#size' => 40,
   );
   $form[] = array(
@@ -226,7 +296,52 @@ function sifr_upload_submit($form_id, $f
 }
 
 /**
- * Edit or create a new rule
+ * Create a new text replacement rule.
+ * 
+ * Since multiple text replacement plugins can support the same font file types
+ * we need to ask for the plugin to use before a rule can be created.
+ */
+function sifr_add_rule() {
+  $form = array();
+  
+  $form['plugin_select'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Text replacement plugin'),
+  );
+  $form['plugin'] = array(
+    '#type' => 'value',
+    '#required' => TRUE,
+  );
+  foreach (sifr_plugins() as $plugin) {
+    $function = $plugin['name'] .'_sifr_setup';
+    if (function_exists($function)) {
+      if ($function()) {
+        $form['plugin_select'][$plugin['name']] = array(
+          '#type' => 'radio',
+          '#title' => $plugin['title'],
+          '#name' => 'plugin',
+          '#return_value' => $plugin['name'],
+        );
+      }
+    }
+  }
+  
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Next'),
+  );
+  
+  return $form;
+}
+
+function sifr_add_rule_submit($form_id, $values) {
+  if (isset($values['plugin'])) {
+    drupal_goto('admin/settings/sifr/edit/'. check_plain($values['plugin']));
+  }
+}
+
+/**
+ * Edit a text replacement rule.
  *
  * @param $edit
  * an array or object of rule parameters
@@ -240,37 +355,60 @@ function sifr_edit_rule($edit = NULL) {
       drupal_goto('admin/settings/sifr/rules/delete/'. $edit);
     }
     else {
-      $edit = sifr_load_rule($edit);
+      $rule = sifr_load_rule($edit);
     }
   }
-  $edit             = (array)$edit;
+  else if (is_string($edit)) {
+    $rule['plugin'] = check_plain($edit);
+  }
+  $font_select = $rule       = (array)$rule;
+  $rules                     = array();
   
-  $swfs             = sifr_get_swfs();
-  $rule->color      = '#000000';
-  $rule->linkcolor  = '#000000';
-  $rule->hovercolor = '#000000';
-  $rule->bgcolor    = '#FFFFFF';
-  $myrules          = array();
-  foreach ($swfs as $path => $name) {
-    $rule->selector = "#font-". str_replace(array(' ', '_'), '-', $name);
-    $rule->font     = $path;
-    $myrules[]      = sifr_render_rule_js($rule);
+  // Override font colors for font selection.
+  $font_select['color']      = '#000000';
+  $font_select['linkcolor']  = '#000000';
+  $font_select['hovercolor'] = '#000000';
+  $font_select['bgcolor']    = '#FFFFFF';
+  $font_select['fontsize']   = '16px';
+  $fonts                     = sifr_get_fonts($rule['plugin']);
+  foreach ($fonts as $path => $name) {
+    $font_select['selector'] = '.sifr-font-'. str_replace(array(' ', '_'), '-', $name);
+    $font_select['font']     = $path;
+    $rules[]                 = sifr_render_rule_js($font_select);
+  }
+  
+  // Ensure that plugin is properly loaded.
+  $function = $rule['plugin'] .'_sifr_load';
+  if (function_exists($function)) {
+    $function();
   }
-  drupal_add_js(sifr_wrap_rules($myrules), 'inline');
+  sifr_css_screen($rule['plugin'], FALSE);
+  drupal_add_css(file_directory_path() .'/sifr/'. $rule['plugin'] .'-screen.css', 'module', 'screen', FALSE);
+  
+  drupal_add_js(sifr_wrap_rules($rule['plugin'], $rules, TRUE), 'inline');
   
-  drupal_add_css(drupal_get_path('module', 'sifr') .'/sifr.css');
+  drupal_add_css(drupal_get_path('module', 'sifr') .'/sifr.css', 'module', 'all', FALSE);
   
-  return drupal_get_form('sifr_rule', $edit);
+  return drupal_get_form('sifr_rule', $rule);
 }
 
 function sifr_rule($edit) {
   if ($edit['rid']) {
-    $form['rid'] = array('#type' => 'hidden', '#value' => $edit['rid']);
-    drupal_set_title(t('Edit sIFR rule %s', array('%s' => $edit['name'])));
+    $form['rid'] = array(
+      '#type' => 'hidden',
+      '#value' => $edit['rid'],
+    );
+    drupal_set_title(t('Edit replacement rule %s', array('%s' => $edit['name'])));
   }
   else {
-    drupal_set_title(t('Add a new sIFR rule'));
+    drupal_set_title(t('Add a new text replacement rule'));
   }
+  $form['plugin'] = array(
+    '#type' => 'value',
+    '#value' => $edit['plugin'],
+    '#required' => TRUE,
+  );
+
   $form['basics'] = array(
     '#type' => 'fieldset',
     '#title' => t('Basics'),
@@ -282,13 +420,11 @@ function sifr_rule($edit) {
     '#required' => TRUE,
     '#description' => t('The human readable name for this rule.'),
   );
-  
   $form['basics']['selector'] = array(
     '#type' => 'textfield',
-    '#title' => t('CSS Selector'),
+    '#title' => t('Selector'),
     '#default_value' => $edit['selector'],
-    '#description' => t('<div>CSS selector for the elements you want to replace.
-    </div>'),
+    '#description' => t('A CSS / HTML selector for the elements you want to replace.'),
     '#required' => TRUE,
   );
   $form['basics']['selectorhelp'] = array(
@@ -299,162 +435,28 @@ function sifr_rule($edit) {
   );
   $form['basics']['selectorhelp']['contents'] = array(
     '#type' => 'markup',
-    '#value' => t('<div>The supported CSS selectors are <code>#</code>, <code>&gt;</code> and <code>.</code>. Whitespace is used to select descendants. Please use whitespace only for this, so instead of <code>#foo &gt; p</code> use <code>#foo&gt;p</code>. You can use multiple selectors by seperating them with a comma ("<code>,</code>").</div>
-    <div style="margin-top:5px">Example presets based on Bluemarine default elements:
-    <div> - <a href="" onclick="document.getElementById(\'edit-selector\').value = \'#main .title\';return false;">page title</a></div>
-    <div> - <a href="" onclick="document.getElementById(\'edit-selector\').value = \' .node .title\';return false;">node titles</a></div>
-    <div> - <a href="" onclick="document.getElementById(\'edit-selector\').value = \' .block .title\';return false;">block titles</a></div>
-    <div> - <a href="" onclick="document.getElementById(\'edit-selector\').value = \' .comment .title\';return false;">comment titles</a></div>
-    Selectors may be dependent on your theme. Examine html source to find appropriate classes and ids being output by your current theme.
-    </div>'),
-  );
-  
-  $form['basics']['font'] = array(
-    '#type' => 'fieldset',
-  );
-  $form['basics']['font']['font'] = array(
-    '#title' => t('Font'),
-    '#type' => 'item',
-    '#description' => t("Select a font to use for this rule."),
-    '#required' => TRUE,
-  );
-  
-  $swfs = sifr_get_swfs();
-  foreach ($swfs as $path => $name) {
-    $form['basics']['font']['font'][]['font'] = array(
-      '#type' => 'radio',
-      '#title' => "<span id='font-". str_replace(array(' ', '_'), '-', $name) ."' class='sifr-font'>". $name .'</span>',
-      '#return_value' => $path,
-      '#default_value' => $edit['font'],
-    );
-  }
-  
-  $form['colors'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Colors'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#description' => t('Each rule defines the colors for replaced text. Use six-character hexadecimal (CSS-style) color values preceeded by &quot;<code>#</code>&quot; to define text color, linked text color, hover-over link color, and text background color.'),
+    '#value' => t('<p>The supported CSS selectors are <code>#</code>, <code>&gt;</code> and <code>.</code>. Whitespace is used to select descendants. Please use whitespace only for this, so instead of <code>#foo &gt; p</code> use <code>#foo&gt;p</code>. You may use multiple selectors by seperating them with a comma ("<code>,</code>").</p>
+    <p>Example presets based on Bluemarine default elements:</p>
+    <ul>
+    <li><a href="" onclick="document.getElementById(\'edit-selector\').value = \'#main .title\';return false;">page title</a></li>
+    <li><a href="" onclick="document.getElementById(\'edit-selector\').value = \' .node .title\';return false;">node titles</a></li>
+    <li><a href="" onclick="document.getElementById(\'edit-selector\').value = \' .block .title\';return false;">block titles</a></li>
+    <li><a href="" onclick="document.getElementById(\'edit-selector\').value = \' .comment .title\';return false;">comment titles</a></li>
+    <p>Selectors may be dependent on your theme. Examine html source to find appropriate classes and ids being output by your current theme.</p>'),
   );
-  $form['colors']['color'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Text Color'),
-    '#size' => 12,
-    '#default_value' => $edit['color'] ? $edit['color'] : '#000000',
-  );
-  $form['colors']['linkcolor'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Link Color'),
-    '#size' => 12,
-    '#default_value' => $edit['linkcolor'] ? $edit['linkcolor'] : '#000000',
-  );
-  $form['colors']['hovercolor'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Hover Color'),
-    '#size' => 12,
-    '#default_value' => $edit['hovercolor'] ? $edit['hovercolor'] : '#666666',
-  );
-  $form['colors']['bgcolor'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Text Background Color'),
-    '#size' => 12,
-    '#default_value' => $edit['bgcolor'] ? $edit['bgcolor'] : '#FFFFFF',
-  );
-  $transparency_options = array(0 => t('No transparency'), 1 => t('Use transparency'), 2 => t('Opaque background'));
-  $form['colors']['transparent'] = array(
-    '#title' => t('Transparency'),
-    '#type' => 'radios',
-    '#options' => $transparency_options,
-    '#default_value' => $edit['transparent'],
-    '#description' => t('<strong>Warning:</strong> Flash transparency is not well supported in all browsers and therefore not recommended.'),
-  );
-  $form['tweaks'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Tweaks'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-  $form['tweaks']['letterspacing'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Letter Spacing'),
-    '#description' => t('A CSS value to adjust letter spacing on replaced text. Examples: &quot;-5px&quot;, &quot;-.2em&quot;'),
-    '#size' => 12,
-    '#default_value' => $edit['letterspacing'],
-  );
-  $form['tweaks']['fontsize'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Font Size'),
-    '#description' => t('A CSS value to adjust font size on replaced text. Examples: &quot;55px&quot;, &quot;2em&quot;'),
-    '#size' => 12,
-    '#default_value' => $edit['fontsize'],
-  );
-  $form['tweaks']['padding'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Padding'),
-    '#description' => '<div style="display:block">'. t('If you use padding in the elements you want to replace, you have to set the amount of padding here (in pixels, but without the px part)') .'</div>',
-    '#prefix' => '<div class="container-inline">',
-    '#suffix' => '</div>',
-  );
-  $form['tweaks']['padding']['paddingtop'] = array(
-    '#type' => 'textfield',
-    '#title' => t('top'),
-    '#size' => 6,
-    '#default_value' => $edit['paddingtop'],
-  );
-  $form['tweaks']['padding']['paddingright'] = array(
-    '#type' => 'textfield',
-    '#title' => t('right'),
-    '#size' => 6,
-    '#default_value' => $edit['paddingright'],
-  );
-  $form['tweaks']['padding']['paddingbottom'] = array(
-    '#type' => 'textfield',
-    '#title' => t('bottom'),
-    '#size' => 6,
-    '#default_value' => $edit['paddingbottom'],
-  );
-  $form['tweaks']['padding']['paddingleft'] = array(
-    '#type' => 'textfield',
-    '#title' => t('left'),
-    '#size' => 6,
-    '#default_value' => $edit['paddingleft'],
-  );
-  $form['tweaks']['textalign'] = array(
-    '#type' => 'select',
-    '#title' => t('Text Alignment'),
-    '#default_value' => $edit['textalign'],
-    '#options' => array(
-      'left' => t('Left'),
-      'center' => t('Center'),
-      'right' => t('Right'),
-    ),
-  );
-  $form['tweaks']['lettercase'] = array(
-    '#type' => 'select',
-    '#title' => t('Case Transform'),
-    '#default_value' => $edit['lettercase'],
-    '#options' => array(
-      'normal' => t('Normal'),
-      'upper' => t('Upper-Case'),
-      'lower' => t('Lower-Case'),
-    ),
-    '#description' => t('You can transform the text to be all upper-case or all lower-case'),
-  );
-  $form['tweaks']['underline'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Underline Links on Hover?'),
-    '#default_value' => $edit['underline'],
-    '#return_value' => 1,
-  );
-  
-  // weight may not be necessary
-  $form['weight'] = array(
+  $form['basics']['weight'] = array(
     '#type' => 'weight',
     '#title' => t('Weight'),
-    '#description' => t('You can adjust the order in which the rules are executed by changing their weights. Lighter items execute before heavier.'),
+    '#description' => t('You can adjust the order in which the rules are executed by changing their weights. Lighter items are executed before heavier.'),
     '#delta' => 10,
-    '#default_value' => $edit['weight'],
+    '#default_value' => isset($edit['weight']) ? $edit['weight'] : 0,
   );
+  
+  $function = $edit['plugin'] .'_sifr_rule';
+  if (function_exists($function)) {
+    $function($form, $edit);
+  }
+  
   $form['submits'] = array(
     '#prefix' => '<div class="container-inline">',
     '#suffix' => '</div>',
@@ -472,22 +474,28 @@ function sifr_rule_submit($form_id, $for
   return 'admin/settings/sifr';
 }
 
-function sifr_delete_confirm($rid) {
-  if (!($rule = sifr_load_rule($rid))) {
+function sifr_delete_rule_form($rid = NULL) {
+  if (!isset($rid) || !($rule = sifr_load_rule($rid))) {
     drupal_goto('admin/settings/sifr');
   }
-  return drupal_get_form('sifr_delete_confirm_form', $rule);
-}
-
-function sifr_delete_confirm_form($rule) {
-  return confirm_form(array('sifr_rid' => array('#type' => 'hidden', '#value' => $rule->rid)), t("Are you sure you want to delete the rule: ") . $rule->name, 'admin/settings/sifr');
+  $form = array();
+  $form['rid'] = array(
+    '#type' => 'hidden',
+    '#value' => $rule['rid'],
+  );
+  return confirm_form($form, t('Are you sure you want to delete the rule @rule?', array('@rule' => $rule['name'])), 'admin/settings/sifr');
 };
 
-function sifr_delete_confirm_form_submit($form_id, $form_values) {
+function sifr_delete_rule_form_submit($form_id, $form_values) {
   if ($form_values['op'] == t('Confirm')) {
-    sifr_rule_delete($form_values['sifr_rid']);
-    sifr_css_screen(FALSE);
+    $rule = sifr_load_rule($form_values['rid']);
+    db_query('DELETE FROM {sifr} WHERE rid = %d', $form_values['rid']);
+    drupal_set_message(t('Rule %name has been deleted.', array('%name' => $rule['name'])));
+    sifr_render_rules_js($rule['plugin']);
+    sifr_css_screen($rule['plugin'], FALSE);
   }
+
+  return 'admin/settings/sifr';
 }
 
 function sifr_fontdelete_confirm() {
@@ -521,23 +529,62 @@ function sifr_fontdelete_confirm_form_su
 }
 
 /**
- * Scans both files/sifr and modules/sifr/sifr for .swf font files
+ * Return a form items to select a font.
+ * 
+ * @param array $form
+ *   A form array containing rule definitions.
+ * @param string $field
+ *   A field name to append the selection.
+ */
+function sifr_font_select($form, $field) {
+  $fonts = sifr_get_fonts($form['plugin']);
+  
+  $font_select = array();
+  foreach ($fonts as $path => $name) {
+    $font_select[][$field] = array(
+      '#type' => 'radio',
+      '#title' => '<span class="sifr-font-'. str_replace(array(' ', '_'), '-', $name) .' sifr-font">'. $name .'</span>',
+      '#return_value' => $path,
+      '#default_value' => $form[$field],
+    );
+  }
+  
+  return $font_select;
+}
+
+/**
+ * Scans files/sifr and modules/sifr/sifr for font files
  *
  * @return array
- * keyed array for select field
+ *   Keyed array for select field.
+ * 
+ * @todo Search in current theme instead of module folder for font files.
  */
-function sifr_get_swfs() {
+function sifr_get_fonts($plugin = NULL) {
   $selects = array();
   
-  // scan files/sifr
+  // Build file mask.
+  $mask = array();
+  $plugins = sifr_plugins();
+  if (isset($plugin) && isset($plugins[$plugin])) {
+    $mask = $plugins[$plugin]['file_masks'];
+  }
+  else {
+    foreach ($plugins as $plugin) {
+      $mask = array_merge($mask, $plugin['file_masks']);
+    }
+  }
+  $mask = implode('|', $mask);
+  
+  // Scan files/sifr.
   $dir = file_create_path('sifr');
-  $listings = file_scan_directory($dir, '.*\.(swf)', array('.', '..', 'CVS', '.svn'), 0, FALSE);
+  $listings = file_scan_directory($dir, $mask, array('.', '..', 'CVS', '.svn'), 0, FALSE);
   foreach ((array)$listings as $listing) {
     $selects[$listing->filename] = $listing->name;
   }
-  // scan modules/sifr/sifr
+  // Scan modules/sifr/sifr.
   $dir = sifr_find_sifr();
-  $listings = file_scan_directory($dir, '.*\.(swf)', array('.', '..', 'CVS', '.svn'), 0, FALSE);
+  $listings = file_scan_directory($dir, $mask, array('.', '..', 'CVS', '.svn'), 0, FALSE);
   foreach ((array)$listings as $listing) {
     $selects[$listing->filename] = $listing->name;
   }
@@ -545,16 +592,28 @@ function sifr_get_swfs() {
 }
 
 /**
- * Get list of rules from db
+ * Fetch rules from database.
+ *
+ * @param string $plugin
+ *   An optional plugin name to limit the query.
  *
  * @return
- * array of rule objects
+ *   An array of rules.
  */
-function sifr_get_rules() {
+function sifr_get_rules($plugin = NULL) {
   $rules = array();
-  $result = db_query('SELECT * FROM {sifr} ORDER BY weight');
-  while ($r = db_fetch_object($result)) {
-    $rules[$r->rid] = $r;
+  if (isset($plugin)) {
+    $result = db_query("SELECT * FROM {sifr} WHERE plugin = '%s' ORDER BY weight", check_plain($plugin));
+  }
+  else {
+    $result = db_query('SELECT * FROM {sifr} ORDER BY weight');
+  }
+  while ($rule = db_fetch_array($result)) {
+    // Unserialize custom plugin properties.
+    $properties = unserialize($rule['properties']);
+    $rule = array_merge($rule, $properties);
+    
+    $rules[$rule['rid']] = $rule;
   }
   return $rules;
 }
@@ -566,7 +625,13 @@ function sifr_get_rules() {
  * the rule
  */
 function sifr_load_rule($rid) {
-  return db_fetch_object(db_query('SELECT * FROM {sifr} WHERE rid = %d', $rid));
+  $rule = db_fetch_array(db_query('SELECT * FROM {sifr} WHERE rid = %d', $rid));
+  
+  // Unserialize custom plugin properties.
+  $properties = unserialize($rule['properties']);
+  $rule = array_merge($rule, $properties);
+  
+  return $rule;
 }
 
 /**
@@ -583,6 +648,17 @@ function sifr_save_rule($edit) {
   else {
     $edit['rid'] = db_next_id('sifr');
   }
+  
+  // Serialize custom plugin properties.
+  $plugins = sifr_plugins();
+  $properties = array();
+  foreach ($edit as $key => $val) {
+    if (is_string($key) && in_array($key, $plugins[$edit['plugin']]['properties'])) {
+      $properties[$key] = $val;
+      unset($edit[$key]);
+    }
+  }
+  
   foreach ($edit as $key => $val) {
     if (in_array($key, _sifr_fields())) {
       // only save keyed values
@@ -603,19 +679,30 @@ function sifr_save_rule($edit) {
       }
     }
   }
+  
+  // Add custom plugin properties field.
+  $keys[]    = 'properties';
+  $valsubs[] = '\'%s\'';
+  $vals[]    = serialize($properties);
+  
   $keys = implode(', ', $keys);
   $valsubs = implode(', ', $valsubs);
   db_query("INSERT INTO {sifr} ($keys) VALUES ($valsubs) ", $vals);
-  drupal_set_message($edit['name'] .' '. t('saved.'));
-  // create the file
-  sifr_css_screen(FALSE);
-  variable_del('sifr_rules');
+  drupal_set_message(t('%rule saved.', array('%rule' => $edit['name'])));
+  // Create the CSS file.
+  sifr_css_screen($edit['plugin'], FALSE);
+  // Create the JS file.
+  sifr_render_rules_js($edit['plugin']);
 }
 
-function sifr_rule_delete($rid) {
-  db_query('DELETE FROM {sifr} WHERE rid = %d', $rid);
-  drupal_set_message(t('Rule deleted.'));
-  variable_del('sifr_rules');
+function sifr_duplicate_rule($rid) {
+  $rule = sifr_load_rule($rid);
+  $rule['name'] = $rule['name'] .' (dupe)';
+  unset($rule['rid']);
+  unset($rule['properties']);
+  sifr_save_rule($rule);
+  
+  drupal_goto('admin/settings/sifr');
 }
 
 function sifr_font_delete($fontfile) {
@@ -648,99 +735,105 @@ function sifr_find_sifr() {
   return $file->dir ? $file->dir : FALSE;
 }
 
+/**
+ * Add JavaScripts and Stylesheets for all active plugins to all pages.
+ */
 function sifr_all_pages() {
   static $done;
   if ($done) {
     return;
   }
-  $sifrdir = variable_get('sifr_dir', FALSE);
-  // no variable set? scan the dir
-  if (!$sifrdir) {
-    $sifrdir = urlencode(sifr_find_sifr());
-  }
-  // still can't find it? display an error
-  if (!$sifrdir) {
-    drupal_set_message(t('The sIFR library is in not installed correctly. Please download from <a href="http://www.mikeindustries.com/sifr/">http://www.mikeindustries.com/sifr/</a> and place in Drupal\'s modules/sifr/ directory.'), 'error');
-  }
-  else {
-    $path = $sifrdir .'/';
-    drupal_add_css(file_directory_path() .'/sifr/sifr-screen.css', 'module', 'screen');
-    drupal_add_css($path .'sIFR-print.css', 'module', 'print');
-    drupal_add_js($path .'sifr.js');
-    drupal_add_js(sifr_render_rules_js(), 'inline');
+  $plugins = sifr_plugins();
+  $active_plugins = variable_get('sifr_plugins', array());
+  $dir = file_directory_path() .'/sifr/';
+  foreach ($plugins as $plugin) {
+    if (in_array($plugin['name'], $active_plugins)) {
+      $function = $plugin['name'] .'_sifr_load';
+      if (function_exists($function)) {
+        $function();
+      }
+      drupal_add_css($dir . $plugin['name'] .'-screen.css', 'module', 'screen');
+      drupal_add_js($dir . $plugin['name'] .'-rules.js');
+    }
   }
   $done = true;
 }
 
-function sifr_render_rules_js() {
-  $rules = variable_get('sifr_rules', null);
-  if (isset($rules)) {
-    return $rules;
+/**
+ * Save text replacement JavaScript files for each plugin.
+ */
+function sifr_render_rules_js($edited_plugin = NULL) {
+  $dir = file_create_path('sifr');
+  if (!file_check_directory($dir, 1)) {
+    drupal_set_message(t('Cannot create directory %sifr.', array('%sifr' => $dir)), 'error');
+    return;
   }
+  
   $rules = array();
+  
+  // Render JavaScript rules for each plugin.
+  foreach (sifr_get_rules($edited_plugin) as $rule) {
+    $rules[$rule['plugin']][] = sifr_render_rule_js($rule);
+  }
+  
+  // Wrap rules with execution handler and save to files.
+  foreach ($rules as $plugin => $plugin_rules) {
+    $plugin_js = sifr_wrap_rules($plugin, $plugin_rules);
+    $filename = check_plain($plugin) .'-rules.js';
+    if ($file = file_save_data($plugin_js, $dir .'/'. $filename, FILE_EXISTS_REPLACE)) {
+      drupal_set_message(t('JavaScript file !file saved.', array('!file' => l($filename, $dir .'/'. $filename))));
+    }
+    else {
+      drupal_set_message(t('JavaScript file !file could not be saved.', array('!file' => $dir .'/'. $filename)), 'error');
+    }
+  }
+
+  // Store active plugins in a variable.
+  $active_plugins = array();
   foreach (sifr_get_rules() as $rule) {
-    $rules[] = sifr_render_rule_js($rule);
+    $active_plugins[$rule['plugin']] = $rule['plugin'];
   }
-  $output = sifr_wrap_rules($rules);
-  variable_set('sifr_rules', $output);
-  return $output;
+  variable_set('sifr_plugins', $active_plugins);
 }
 
+/**
+ * Return a single JavaScript text replacement rule.
+ */
 function sifr_render_rule_js($rule) {
-  $properties = array();
-  // convert spaces in filename
-  $fontpath = base_path() . str_replace('%2F', '/', rawurlencode($rule->font));
-  $properties['sFlashSrc'] = $fontpath;
-  $properties['sColor'] = $rule->color;
-  $properties['sLinkColor'] = $rule->linkcolor;
-  $properties['sHoverColor'] = $rule->hovercolor;
-  $properties['sBgColor'] = $rule->bgcolor;
-  $properties['nPaddingTop'] = is_numeric($rule->paddingtop) ? $rule->paddingtop : null;
-  $properties['nPaddingRight'] = is_numeric($rule->paddingright) ? $rule->paddingright : null;
-  $properties['nPaddingBottom'] = is_numeric($rule->paddingbottom) ? $rule->paddingbottom : null;
-  $properties['nPaddingLeft'] = is_numeric($rule->paddingleft) ? $rule->paddingleft : null;
-  switch ($rule->transparent) {
-    case '1':
-      $properties['sWmode'] = 'transparent';
-      break;
-    
-    case '2':
-      $properties['sWmode'] = 'opaque';
-      break;
-  }
-  $properties['sCase'] = in_array($rule->lettercase, array('upper', 'lower')) ? $rule->lettercase : '';
-  $vars = array();
-  if ($rule->underline) {
-    $vars[] = 'underline=true';
+  $plugins = sifr_plugins();
+  $function = $rule['plugin'] .'_sifr_render_rule_js';
+  if (function_exists($function)) {
+    return $function($rule);
   }
-  if (in_array($rule->textalign, array('left', 'center', 'right'))) {
-    $vars[] = 'textalign='. $rule->textalign;
-  }
-  $properties['sFlashVars'] = implode($vars, '&');
-  
-  $output = "  sIFR.replaceElement(\"$rule->selector\", named({";
-  foreach ($properties as $property => $value) {
-    if (!empty($value)) {
-      $output .= $property .': "'. $value .'", ';
-    }
+  else {
+    $message = t("Error: @plugin does not provide a method to build JavaScript text replacement invocations.", array('@plugin' => $rule['plugin']));
+    drupal_set_message($message, 'error');
+    return '  /* '. $message ." */\n";
   }
-  $output = substr($output, 0, -2);
-  $output .= "}));\n";
-  return $output;
 }
 
-function sifr_wrap_rules($rules) {
+/**
+ * Wrap execution handler around JavaScript rules.
+ */
+function sifr_wrap_rules($plugin, $rules, $inline = FALSE) {
+  $output = '';
+  if (is_array($rules) && $inline) {
+    $output .= "\n//<![CDATA['\n";
+  }
   if (is_array($rules)) {
-    $output = "\n//<![CDATA['\n";
-    $output .= "if(typeof sIFR == \"function\") {\n";
-    $output .= implode("  ", $rules);
-    $output .= "};\n";
+    $plugins = sifr_plugins();
+    $function = $plugin .'_sifr_wrap_rules';
+    if (function_exists($function)) {
+      $output .= $function($rules);
+    }
+  }
+  if (is_array($rules) && $inline) {
     $output .= "//]]>\n";
   }
   return $output;
 }
 
-function sifr_css_screen($print = TRUE) {
+function sifr_css_screen($plugin, $print = TRUE) {
   if ($print) {
     header("Content-type: text/css");
     header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
@@ -749,58 +842,31 @@ function sifr_css_screen($print = TRUE) 
     header("Cache-Control: post-check=0, pre-check=0", FALSE);
     header("Pragma: no-cache");
   }
-  $output = "/* These are standard sIFR styles... do not modify */
-
-.sIFR-flash {
-  visibility: visible !important;
-  margin: 0;
-}
-
-.sIFR-replaced {
-  visibility: visible !important;
-}
-
-span.sIFR-alternate {
-  position: absolute;
-  left: 0;
-  top: 0;
-  width: 0;
-  height: 0;
-  display: block;
-  overflow: hidden;
-}
-
-/* Hide Adblock Object tab: the text should show up just fine, not poorly with a tab laid over it. */
-.sIFR-flash + div[adblocktab=true] {
-  display: none !important;
-}
-
-/* These \"decoy\" styles are used to hide the browser text before it is replaced... the negative-letter spacing in this case is used to make the browser text metrics match up with the sIFR text metrics since the sIFR text in this example is so much narrower... your own settings may vary... any weird sizing issues you may run into are usually fixed by tweaking these decoy styles */
-
-";
   
-  foreach (sifr_get_rules() as $rule) {
-    $fontsize       = trim($rule->fontsize) ? "  font-size: $rule->fontsize;\n" : '';
-    $letterspacing  = trim($rule->letterspacing) ? "  letter-spacing: $rule->letterspacing;\n" : '';
-    $rule->selector = str_replace(',', ', .sIFR-hasFlash ', $rule->selector);
-    $output .= "
-.sIFR-hasFlash $rule->selector {
-  visibility: hidden;
-$fontsize$letterspacing}
-";
+  $plugin   = check_plain($plugin);
+  $plugins  = sifr_plugins();
+  $function = $plugin .'_sifr_css_screen';
+  if (function_exists($function)) {
+    $output = $function(sifr_get_rules($plugin));
+  }
+  else {
+    $message = t("Error: @plugin does not provide a method to build a CSS file.", array('@plugin' => $plugin));
+    drupal_set_message($message, 'error');
+    return '  /* '. $message ." */\n";
   }
+  
   $dir = file_create_path('sifr');
-  // creates directory if needed
   if (file_check_directory($dir, 1)) {
-    if ($file = file_save_data($output, $dir .'/sifr-screen.css', FILE_EXISTS_REPLACE)) {
-      drupal_set_message(t('CSS file saved.'));
+    $filename = $plugin .'-screen.css';
+    if ($file = file_save_data($output, $dir .'/'. $filename, FILE_EXISTS_REPLACE)) {
+      drupal_set_message(t('CSS file !file saved.', array('!file' => l($filename, $dir .'/'. $filename))));
     }
     else {
-      drupal_set_message(t('CSS file could not be saved.'), 'error');
+      drupal_set_message(t('CSS file !file could not be saved.', array('!file' => $dir .'/'. $filename)), 'error');
     }
   }
   else {
-    drupal_set_message(t('Cannot create sifr directory in files.'), 'error');
+    drupal_set_message(t('Cannot create directory %sifr.', array('%sifr' => $dir)), 'error');
   }
   if ($print) {
     print $output;
@@ -812,7 +878,7 @@ $fontsize$letterspacing}
  * Makes txt files download rather than appearing in browser
  *
  * @param string $file
- * The file to be downloaded
+ *   The file to be downloaded
  */
 function sifr_octet_download($file) {
   $filepath = sifr_find_sifr() .'/'. $file;
@@ -830,6 +896,6 @@ function sifr_octet_download($file) {
 }
 
 function _sifr_fields() {
-  return array('rid', 'name', 'font', 'selector', 'color', 'linkcolor', 'hovercolor', 'bgcolor', 'transparent', 'weight', 'letterspacing', 'fontsize', 'paddingtop', 'paddingright', 'paddingbottom', 'paddingleft', 'textalign', 'underline', 'lettercase');
+  return array('rid', 'plugin', 'name', 'selector', 'properties', 'weight');
 }
 
Index: plugins/sifr.inc
===================================================================
RCS file: plugins/sifr.inc
diff -N plugins/sifr.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ plugins/sifr.inc	19 Aug 2007 22:44:51 -0000
@@ -0,0 +1,365 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Text replacement API sIFR plugin.
+ */
+
+/**
+ * Return plugin information.
+ */
+function sifr_sifr_plugin() {
+  return array(
+    'name' => 'sifr',
+    'title' => 'sIFR',
+    'url' => 'http://www.mikeindustries.com/sifr/',
+    'dependencies' => array('sifr.js', 'sIFR-print.css'),
+    'file_masks' => array('.+\.swf'),
+    'properties' => array('font', 'color', 'linkcolor', 'hovercolor', 'bgcolor', 'transparent', 'letterspacing', 'fontsize', 'paddingtop', 'paddingright', 'paddingbottom', 'paddingleft', 'textalign', 'lettercase', 'underline'),
+  );
+}
+
+/**
+ * Return plugin instructions.
+ */
+function sifr_sifr_plugin_instructions() {
+  return t('
+  <h3>Good places to find sIFR fonts:</h3>
+  <ul>
+    <li><a href="http://sifrfonts.com/">SIFRfonts.com</a></li>
+    <li><a href="http://www.fontsmack.com/">Font Smack</a></li>
+    <li><a href="http://www.isarie.com/?p=17">Stefan Isarie</a></li>
+  </ul>
+  <p>Just download \'em from there and upload \'em here.</p>
+  
+  <h3>To create your own sIFR font files:</h3>
+    <ol>
+      <li>Download these files to the same directory: !files</li>
+      <li>Open sifr.fla in Macromedia Flash.</li>
+      <li>Double-click the invisible textbox in the middle of the stage. If the &quot;Properties&quot; palette is not already visible, open it by selecting &quot;Window > Properties&quot;, and select which font you\'d like to use from the drop down menu. If you select a TrueType font, you can also create bold and italic styles for your font by clicking on the &quot;I&quot; or &quot;B&quot; buttons. The standard sifr.fla file contains most of the English characters you will generally need. If you need to embed additional characters or languages, click the &quot;Character&quot; button and select more characters from there.</li>
+      <li>To export the new file, choose &quot;File > Export&quot; and save as fontname.swf</li>
+      <li>Upload the your font file below.</li>
+    </ol>',
+    array(
+      '!files' => l('sifr.fla', 'sifr/download/sifr.fla') .', '. l('customize_me.as', 'sifr/download/customize_me.as') .', '. l('dont_customize_me.as', 'sifr/download/dont_customize_me.as')
+      )
+  );
+}
+
+/**
+ * Perform plugin installation checks.
+ */
+function sifr_sifr_setup() {
+  // Check working directory.
+  $dir = file_create_path('sifr');
+  if (!file_check_directory($dir, 1)) {
+    drupal_set_message(t('The sIFR working directory !dir is not writable.', array('!dir' => $dir)), 'error');
+    return FALSE;
+  }
+  
+  return TRUE;
+}
+
+/**
+ * Returns font(s) and colors of a text replacement rule.
+ * 
+ * @param array $rule
+ *   A text replacement rule.
+ * 
+ * @return array $fontstyle
+ *   An array containing the keys
+ *   - font
+ *   - colors
+ *     - color
+ *     - linkcolor (optional)
+ *     - hovercolor (optional)
+ *     - bgcolor (optional)
+ */
+function sifr_sifr_rules($rule) {
+  $fontstyle = array();
+  $fontstyle['font'] = substr($rule['font'], strrpos($rule['font'], '/') + 1);
+  $fontstyle['colors'] = array(
+    'color' => $rule['color'],
+    'linkcolor' => $rule['linkcolor'],
+    'hovercolor' => $rule['hovercolor'],
+    'bgcolor' => $rule['bgcolor'],
+  );
+  
+  return $fontstyle;
+}
+
+/**
+ * Return custom rule properties.
+ * 
+ * @param array $form
+ *   A rule edit form, passed by reference.
+ * @param array $edit
+ *   User values for the form.
+ */
+function sifr_sifr_rule(&$form, $edit) {
+  $form['font'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Font'),
+  );
+  $form['font']['font'] = array(
+    '#title' => t('Font'),
+    '#type' => 'item',
+    '#description' => t("Select a font to use for this rule."),
+    '#required' => TRUE,
+  );
+  $form['font']['font']['fonts'] = sifr_font_select($edit, 'font');
+  
+  $form['colors'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Colors'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Each rule defines the colors for replaced text. Use hexadecimal (CSS-style) color values preceeded by &quot;<code>#</code>&quot; to define text color, linked text color, hover-over link color, and text background color.'),
+  );
+  $form['colors']['color'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Text Color'),
+    '#size' => 12,
+    '#default_value' => $edit['color'] ? $edit['color'] : '#000000',
+  );
+  $form['colors']['linkcolor'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Link Color'),
+    '#size' => 12,
+    '#default_value' => $edit['linkcolor'] ? $edit['linkcolor'] : '#000000',
+  );
+  $form['colors']['hovercolor'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Hover Color'),
+    '#size' => 12,
+    '#default_value' => $edit['hovercolor'] ? $edit['hovercolor'] : '#666666',
+  );
+  $form['colors']['bgcolor'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Text Background Color'),
+    '#size' => 12,
+    '#default_value' => $edit['bgcolor'] ? $edit['bgcolor'] : '#FFFFFF',
+  );
+  $transparency_options = array(
+    0 => t('No transparency'),
+    1 => t('Use transparency'),
+    2 => t('Opaque background'),
+  );
+  $form['colors']['transparent'] = array(
+    '#title' => t('Transparency'),
+    '#type' => 'radios',
+    '#options' => $transparency_options,
+    '#default_value' => $edit['transparent'],
+    '#description' => t('<strong>Warning:</strong> Flash transparency is not well supported in all browsers and therefore not recommended.'),
+  );
+  $form['tweaks'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Tweaks'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['tweaks']['letterspacing'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Letter Spacing'),
+    '#description' => t('A CSS value to adjust letter spacing on replaced text. Examples: &quot;-5px&quot;, &quot;-.2em&quot;'),
+    '#size' => 12,
+    '#default_value' => $edit['letterspacing'],
+  );
+  $form['tweaks']['fontsize'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Font Size'),
+    '#description' => t('A CSS value to adjust font size on replaced text. Examples: &quot;55px&quot;, &quot;2em&quot;'),
+    '#size' => 12,
+    '#default_value' => $edit['fontsize'],
+  );
+  $form['tweaks']['padding'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Padding'),
+    '#description' => '<div style="display:block">'. t('If you use padding in the elements you want to replace, you have to set the amount of padding here (in pixels, but without the px part)') .'</div>',
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['tweaks']['padding']['paddingtop'] = array(
+    '#type' => 'textfield',
+    '#title' => t('top'),
+    '#size' => 6,
+    '#default_value' => $edit['paddingtop'],
+  );
+  $form['tweaks']['padding']['paddingright'] = array(
+    '#type' => 'textfield',
+    '#title' => t('right'),
+    '#size' => 6,
+    '#default_value' => $edit['paddingright'],
+  );
+  $form['tweaks']['padding']['paddingbottom'] = array(
+    '#type' => 'textfield',
+    '#title' => t('bottom'),
+    '#size' => 6,
+    '#default_value' => $edit['paddingbottom'],
+  );
+  $form['tweaks']['padding']['paddingleft'] = array(
+    '#type' => 'textfield',
+    '#title' => t('left'),
+    '#size' => 6,
+    '#default_value' => $edit['paddingleft'],
+  );
+  $form['tweaks']['textalign'] = array(
+    '#type' => 'select',
+    '#title' => t('Text Alignment'),
+    '#default_value' => $edit['textalign'],
+    '#options' => array(
+      'left' => t('Left'),
+      'center' => t('Center'),
+      'right' => t('Right'),
+    ),
+  );
+  $form['tweaks']['lettercase'] = array(
+    '#type' => 'select',
+    '#title' => t('Case Transform'),
+    '#default_value' => $edit['lettercase'],
+    '#options' => array(
+      'normal' => t('Normal'),
+      'upper' => t('Upper-Case'),
+      'lower' => t('Lower-Case'),
+    ),
+    '#description' => t('You can transform the text to be all upper-case or all lower-case'),
+  );
+  $form['tweaks']['underline'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Underline Links on Hover?'),
+    '#default_value' => $edit['underline'],
+    '#return_value' => 1,
+  );
+}
+
+/**
+ * Render a single Javascript text replacement rule.
+ */
+function sifr_sifr_render_rule_js($rule) {
+  $properties = array();
+  // convert spaces in filename
+  $fontpath = base_path() . str_replace('%2F', '/', rawurlencode($rule['font']));
+  $properties['sFlashSrc']      = $fontpath;
+  $properties['sColor']         = $rule['color'];
+  $properties['sLinkColor']     = $rule['linkcolor'];
+  $properties['sHoverColor']    = $rule['hovercolor'];
+  $properties['sBgColor']       = $rule['bgcolor'];
+  $properties['nPaddingTop']    = is_numeric($rule['paddingtop']) ? $rule['paddingtop'] : null;
+  $properties['nPaddingRight']  = is_numeric($rule['paddingright']) ? $rule['paddingright'] : null;
+  $properties['nPaddingBottom'] = is_numeric($rule['paddingbottom']) ? $rule['paddingbottom'] : null;
+  $properties['nPaddingLeft']   = is_numeric($rule['paddingleft']) ? $rule['paddingleft'] : null;
+  switch ($rule['transparent']) {
+    case '1':
+      $properties['sWmode'] = 'transparent';
+      break;
+    
+    case '2':
+      $properties['sWmode'] = 'opaque';
+      break;
+  }
+  $properties['sCase'] = in_array($rule['lettercase'], array('upper', 'lower')) ? $rule['lettercase'] : '';
+  $vars = array();
+  if ($rule['underline']) {
+    $vars[] = 'underline=true';
+  }
+  if (in_array($rule['textalign'], array('left', 'center', 'right'))) {
+    $vars[] = 'textalign='. $rule['textalign'];
+  }
+  $properties['sFlashVars'] = implode($vars, '&');
+  
+  $output = 'sIFR.replaceElement("'. $rule['selector'] .'", named({';
+  foreach ($properties as $property => $value) {
+    if (!empty($value)) {
+      $output .= $property .': "'. $value .'", ';
+    }
+  }
+  $output = substr($output, 0, -2);
+  $output .= '}));';
+  
+  return $output;
+}
+
+/**
+ * Wrap execution handler around JavaScript rules.
+ */
+function sifr_sifr_wrap_rules($rules) {
+  $output = '';
+  if (is_array($rules)) {
+    $output .= "if(typeof sIFR == \"function\") {\n";
+    foreach ($rules as $rule) {
+      $output .= '  '. $rule ."\n";
+    }
+    $output .= "};\n";
+  }
+  
+  return $output;
+}
+
+/**
+ * Render a CSS file for this plugin.
+ */
+function sifr_sifr_css_screen($rules) {
+  $output = "/* These are standard sIFR styles... do not modify */
+
+.sIFR-flash {
+  visibility: visible !important;
+  margin: 0;
+}
+
+.sIFR-replaced {
+  visibility: visible !important;
+}
+
+span.sIFR-alternate {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 0;
+  height: 0;
+  display: block;
+  overflow: hidden;
+}
+
+/* Hide Adblock Object tab: the text should show up just fine, not poorly with a tab laid over it. */
+.sIFR-flash + div[adblocktab=true] {
+  display: none !important;
+}
+
+/* These \"decoy\" styles are used to hide the browser text before it is replaced... the negative-letter spacing in this case is used to make the browser text metrics match up with the sIFR text metrics since the sIFR text in this example is so much narrower... your own settings may vary... any weird sizing issues you may run into are usually fixed by tweaking these decoy styles */
+
+";
+  
+  foreach ($rules as $rule) {
+    $fontsize       = trim($rule['fontsize']) ? "  font-size: $rule[fontsize];\n" : '';
+    $letterspacing  = trim($rule['letterspacing']) ? "  letter-spacing: $rule[letterspacing];\n" : '';
+    $rule['selector'] = str_replace(',', ', .sIFR-hasFlash ', $rule['selector']);
+    $output .= "
+.sIFR-hasFlash $rule[selector] {
+  visibility: hidden;
+$fontsize$letterspacing}
+";
+  }
+  
+  return $output;
+}
+
+/**
+ * Load plugin JavaScript and stylesheet files.
+ */
+function sifr_sifr_load() {
+  $sifrdir = variable_get('sifr_dir', FALSE);
+  // no variable set? scan the dir
+  if (!$sifrdir) {
+    $sifrdir = urlencode(sifr_find_sifr());
+  }
+  // still can't find it? display an error
+  if (!$sifrdir) {
+    drupal_set_message(t('The sIFR library is in not installed correctly. Please download from <a href="http://www.mikeindustries.com/sifr/">http://www.mikeindustries.com/sifr/</a> and place in Drupal\'s modules/sifr/ directory.'), 'error');
+  }
+  else {
+    drupal_add_js($sifrdir .'/sifr.js');
+    drupal_add_css($sifrdir .'/sIFR-print.css', 'module', 'print');
+  }
+}
+
Index: plugins/sifr.png
===================================================================
RCS file: plugins/sifr.png
diff -N plugins/sifr.png
Binary files /dev/null and sifr.png differ
