<?php
// $Id: textimage.module,v 1.4 2007/10/11 12:49:43 bremtd Exp $

/**
 * Matches all 'P' Unicode character classes (punctuation)
 */
define('PREG_CLASS_PUNCTUATION',
'\x{21}-\x{23}\x{25}-\x{2a}\x{2c}-\x{2f}\x{3a}\x{3b}\x{3f}\x{40}\x{5b}-\x{5d}'.
'\x{5f}\x{7b}\x{7d}\x{a1}\x{ab}\x{b7}\x{bb}\x{bf}\x{37e}\x{387}\x{55a}-\x{55f}'.
'\x{589}\x{58a}\x{5be}\x{5c0}\x{5c3}\x{5f3}\x{5f4}\x{60c}\x{60d}\x{61b}\x{61f}'.
'\x{66a}-\x{66d}\x{6d4}\x{700}-\x{70d}\x{964}\x{965}\x{970}\x{df4}\x{e4f}'.
'\x{e5a}\x{e5b}\x{f04}-\x{f12}\x{f3a}-\x{f3d}\x{f85}\x{104a}-\x{104f}\x{10fb}'.
'\x{1361}-\x{1368}\x{166d}\x{166e}\x{169b}\x{169c}\x{16eb}-\x{16ed}\x{1735}'.
'\x{1736}\x{17d4}-\x{17d6}\x{17d8}-\x{17da}\x{1800}-\x{180a}\x{1944}\x{1945}'.
'\x{2010}-\x{2027}\x{2030}-\x{2043}\x{2045}-\x{2051}\x{2053}\x{2054}\x{2057}'.
'\x{207d}\x{207e}\x{208d}\x{208e}\x{2329}\x{232a}\x{23b4}-\x{23b6}\x{2768}-'.
'\x{2775}\x{27e6}-\x{27eb}\x{2983}-\x{2998}\x{29d8}-\x{29db}\x{29fc}\x{29fd}'.
'\x{3001}-\x{3003}\x{3008}-\x{3011}\x{3014}-\x{301f}\x{3030}\x{303d}\x{30a0}'.
'\x{30fb}\x{fd3e}\x{fd3f}\x{fe30}-\x{fe52}\x{fe54}-\x{fe61}\x{fe63}\x{fe68}'.
'\x{fe6a}\x{fe6b}\x{ff01}-\x{ff03}\x{ff05}-\x{ff0a}\x{ff0c}-\x{ff0f}\x{ff1a}'.
'\x{ff1b}\x{ff1f}\x{ff20}\x{ff3b}-\x{ff3d}\x{ff3f}\x{ff5b}\x{ff5d}\x{ff5f}-'.
'\x{ff65}');

/**
 * Matches all 'Z' Unicode character classes (separators)
 */
define('PREG_CLASS_SEPARATOR','\x{20}\x{a0}\x{1680}\x{180e}\x{2000}-\x{200a}\x{2028}\x{2029}\x{202f}\x{205f}\x{3000}');

if (module_exists('captcha')) {
	include_once(drupal_get_path('module', 'textimage') .'/captcha.inc');
}

/*
 * Text in image is aligned left.
 */
define('ALIGN_LEFT',1);

/*
 * Text in image is centered.
 */
define('ALIGN_CENTER',2);

/*
 * Text in image is aligned right.
 */
define('ALIGN_RIGHT',3);

/**
* Implementation of hook_help().
*/
function textimage_help($section) {
  $output = "";

  switch ($section) {
    case 'admin/help#textimage':
      $output .= '<h2 id="textimage-introduction">The dynamic text to image generator!</h2>';
      $output .= '<p>Text image is a module which allows you to create dynamic images using any font installed on the server.</p>';
      $output .= '<p>Creating images from text has several important advantages and disadvantages. The greatest advantage is it allows you to use any font you wish for headlines and titles without the user needing to install the font. Another advantage is the wide browser support for images and available accessibility features (alt and title tags for instance), so screen readers should be able to handle your images without problem. You might also prefer text images over the <a href="http://www.mikeindustries.com/sifr/">sIFR approach</a> because generated images are not disabled by many ad-blocking tools. However, an important limitation of Text Image module is wrapping of text, which it does not currently support and cannot compete with the dynamic abilities of a Flash based solution. If you need to create images with wrapped text, sIFR may be a better solution.</p>';
      $output .= '<h2>Installing Fonts</h2>';
      $output .= '<p>Text image is dependent on the GD2 library for text manipulations. You can check what version of GD you have on the <a href="!config">Text Image configuration page</a>. Before you can begin using Text Image you must upload at least one TrueType font to the server and tell Text Image where you uploaded it. All fonts must have the .tff extension to be seen by Text Image. If you do not have any TTF fonts to start using Text Image, you can download some free GNU fonts from the <a href="http://savannah.nongnu.org/projects/freefont/">Free UCS Outline Fonts Project</a>. Once the fonts are uploaded, enter the UNIX-style path the fonts on the <a href="!config">configuration page</a>. If you are using Text Image with the Captcha module, you\'ll also need to set a seperate directory on the <a href="!captcha">Text Image Captcha Settings</a> for fonts to be used at random.</p>';
      $output .= '<h2 id="textimage-configuration">Configuration</h2>';
      $output .= '<p>The basis of Text Image is made of configuration options called <em>presets</em>. A preset defines what font, size, color, etc. should be used in the generated image. You can create a new preset by clicking on the tab by the same name from the <a href="!config">configuration page</a>. Most options are pretty self explanatory, but background images can get pretty complicated if you begin to use other presets as backgrounds. Let\'s run through an example.';
      $output .= '<p>If you specified a backgrounds directory on the main configuration page, a list of backgrounds is automatically popuplated into the Background Image select list. Let\'s say there\'s a image called &quot;header.png&quot; in the image list that looks like this:<p>';
      $output .= '<p><img src="!example1" alt="example1" /></p>';
      $output .= '<p>Now we\'ll create a preset called &quot;preset1&quot;. In this preset, set the font to braggadacio.ttf (not included), 54px, #FFFFFF (white) color. Select header.png from the background list and position the text at x-offset 14 and y-offset 22 (in pixels). After the preset is saved, Text Image is now ready to automatically generate images based on text strings. You could directly visit the image at {files}/textimage/preset1/Hello.png (where {files} is your sites file directory) and get the following result:</p>'; 
      $output .= '<p><img src="!example2" alt="example2" /></p>';
      $output .= '<p>To get crazy now, create a new preset called &quot;preset2&quot;. In this preset, set the font to century.ttf (also not included), 20px, #000000 (black) color. Select <em>preset1</em> from the background list and position the text at x-offset 14 and y-offset 94. Save the preset then visit {files}/textimage/preset2/Hello/world!.png and get the following result:</p>';
      $output .= '<p><img src="!example3" alt="example3" /></p>';
      $output .= '<p>The entire preset1 is generated using the first argument <em>Hello</em>. Then preset2 is generated using the name of the file <em>world!.png</em>. You could continue chaining presets together over and over again, but be sure not to create a loop.</p>';
      $output .= '<h2>File Names and Storage</h2>';
      $output .= '<p>Text Image supports .png, .gif, and .jpg input and output files. You can change the output format of the image simply by changing the extension of the last file. In the above example we made PNG images. If we had appended it with .jpg, a JPG image would have been created. Only PNG and GIF files support transparent backgrounds.</p>';
      $output .= '<p>When directly calling URLs for text images, underscores (_) may be used in the place of spaces.</p>';
      $output = t($output, array(
        '!config' => url('admin/settings/textimage'),
        '!captcha' => url('admin/settings/textimage/captcha'),
        '!example1' => base_path() . drupal_get_path('module', 'textimage') . '/misc/example1.png',
        '!example2' => base_path() . drupal_get_path('module', 'textimage') . '/misc/example2.png',
        '!example3' => base_path() . drupal_get_path('module', 'textimage') . '/misc/example3.png',
      ));
      break;
    case 'admin/build/modules#description':
    case 'admin/build/modules/textimage':
    case 'admin/settings/textimage':
      $output = t('Provides text to image manipulations and image based captcha challenge (with Captcha module).');
      break;
  }
  return $output;
}

/**
* Implementation of hook_menu().
*/
function textimage_menu($may_cache) {
  $items = array();
  
  if ($may_cache) {
  	/**
  	 * The text image itself.
  	 */
    $items[] = array(
      'path' => file_directory_path() . '/textimage',
      'callback' => 'textimage_image',
      'access' => true,
      'type' => MENU_CALLBACK,
    );
    
    /*
     * JavaScript file needed for Dynamic Image Replacement.
     */
    $items[] = array(
      'path' => 'dir.js',
      'callback' => 'textimage_dir_js',
      'access' => variable_get('textimage_mode', 'normal') == 'dynamic',
      'type' => MENU_CALLBACK,
    );
    
    /*
     * General TextImage settings.
     */
    $items[] = array(
      'path' => 'admin/settings/textimage',
      'title' => t('Text Image'),
      'description' => t('Configure text to image preset functions.') . (module_exists('captcha') ? ' ' . t('Set the options for image based captchas.') : ''),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_settings_form'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM
    );    
    $items[] = array(
      'path' => 'admin/settings/textimage/settings',
      'title' => t('Settings'),
      'callback' => 'drupal_get_form', 
      'callback arguments' => array('textimage_settings_form'),
      'access' => user_access('administer site configuration'),
      'weight' => 0,
      'type' => MENU_DEFAULT_LOCAL_TASK
    );
    
    /*
     * Presets.
     */
    $items[] = array(
      'path' => 'admin/settings/textimage/presets',
      'title' => t('Presets'),
      'callback' => 'textimage_preset_list', 
      'access' => user_access('administer site configuration'),
      'weight' => 1,
      'type' => MENU_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/presets/list',
      'title' => t('List'),
      'callback' => 'textimage_preset_list', 
      'access' => user_access('administer site configuration'),
      'weight' => 0,
      'type' => MENU_DEFAULT_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/presets/new',
      'title' => t('New'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_preset_edit', 'new'),
      'access' => user_access('administer site configuration'),
      'weight' => 1,
      'type' => MENU_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/presets/edit',
      'title' => t('Edit Preset'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_preset_edit'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    
    /*
     * Dynamic Image Replacement.
     */
    $items[] = array(
      'path' => 'admin/settings/textimage/dir',
      'title' => t('Dynamic Image Replacement'),
      'callback' => 'textimage_dir_list', 
      'access' => user_access('administer site configuration'),
      'weight' => 2,
      'type' => MENU_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/dir/list',
      'title' => t('List'),
      'callback' => 'textimage_dir_list', 
      'access' => user_access('administer site configuration'),
      'weight' => 0,
      'type' => MENU_DEFAULT_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/dir/new',
      'title' => t('New'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_dir_edit', 'new'),
      'access' => user_access('administer site configuration'),
      'weight' => 1,
      'type' => MENU_LOCAL_TASK
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/dir/edit',
      'title' => t('Dynamic Image Replacement'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_dir_edit'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK
    );
    
    /*
     * Captcha.
     */    
    if (module_exists('captcha')) {
      $items[] = array(
        'path' => 'admin/settings/textimage/captcha',
        'title' => t('Captcha display'),
        'callback' => 'drupal_get_form',
        'callback arguments' => array('textimage_captcha_settings_form'),
        'access' => user_access('administer site configuration'),
        'weight' => 5,
        'type' => MENU_LOCAL_TASK
      );
    }
  }
  else {    
    /*
     * Captcha.
     */
    $suffix = arg(2) != null ? '/' . arg(2) : '';
    $items[] = array(
      'path' => '_textimage/image'.$suffix, 'title' => t('textimage'),
      'callback' => '_textimage_captcha_image', 
      'access' => user_access('access textimages'),
      'type' => MENU_CALLBACK
    ); 
    
    /*
     * Presets.
     */
    $items[] = array(
      'path' => 'admin/settings/textimage/presets/delete/' . arg(5),
      'title' => t('Edit Preset'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_preset_delete_confirm', arg(5)),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/textimage/presets/flush/' . arg(5),
      'title' => t('Flush Preset Cache'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_preset_flush_confirm', arg(5)),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );    
     
    /*
     * Dynamic Image Replacement.
     */
    $items[] = array(
      'path' => 'admin/settings/textimage/dir/delete/' . arg(5),
      'title' => t('Edit DIR'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('textimage_dir_delete_confirm', arg(5)),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
* Implementation of hook_perm().
*/
function textimage_perm() {
  return array('access textimages');
}

/**
* Implementation of hook_init().
*/
function textimage_init() {
	// If TextImage is running in Dynamic Image Replacement Mode, some javascript is added to do the image replacements.
	if(variable_get('textimage_mode', 'normal') == 'dynamic') {
		drupal_add_js('dir.js');
	}
}

function textimage_settings_form() {
  $form = array();

  $fonts_path = variable_get("textimage_fonts_path", "");
  $images_path = variable_get("textimage_images_path", "");
  //$security_key = variable_get("textimage_security_key", "");
  
  $modes = array(
  	'disabled' => t('Disabled'), 
  	'normal' => t('Normal'), 
  	'secure' => t('Secure'), 
  	'dynamic' => t('Dynamic Image Replacement')
  );

  //check for GD
  if (!function_exists(imagecreate)) {
    drupal_set_message(t('Image library not available. Textimage needs the GD library extension to be installed. Please install GD.'), 'error');
  }
  //check for TTF support
  if (!function_exists(imagettftext)) {
    drupal_set_message(t('Your image library does not seem to have TrueType font support. Textimage will work, but will use the default inbuilt font.'), 'error');
  }
  //check for clean URL support
  if (!variable_get('clean_url', false)) {
    drupal_set_message(t('Textimage requires <a href="!path">clean URLs</a> be enabled. Without clean URL support Textimage is unable to cache images and can cause serious performance problems.', array('!path' => url('admin/settings/clean-urls'))), 'error');
  }
  //check for valid font path
  if ($fonts_path != '' && !is_dir(rtrim($fonts_path, '\\/'))) {
    drupal_set_message(t('The current font path is invalid. The default font will be used.'), 'error');
  }
  //check for valid image path
  if ($images_path != '' && !is_dir(rtrim($images_path, '\\/'))) {
    drupal_set_message(t('The current images path is invalid. No images will be used.'), 'error');
  }
  if (isset($fonts_path)) {
    $imagefontinfo .= t('Number of fonts found: ').count(_textimage_font_list($fonts_path));
  }
  if (isset($images_path)) {
    $imagefontinfo .= '<br />'.t('Number of background images found: ').count(_textimage_image_list($images_path));
  }

  $gdinfo = gd_info();
  $imagefontinfo .= '<br />'.t('GD Version: ').$gdinfo["GD Version"];
  $imagefontinfo .= '<br />'.t(' FreeType Support: ');
  $imagefontinfo .= ($gdinfo["FreeType Support"] == true) ? t('True') : t('False');
  $imagefontinfo .= '<br />';
  
  if (!$gdinfo["FreeType Support"]) {
    drupal_set_message(t("This server's installation of GD does not have FreeType support. Textimage requires FreeType support for proper functionality."), 'error');
  }
  
  $form['info'] = array(
    '#type' => 'fieldset',
    '#title' => t('Image and font information'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE
  );
  
  $form['info']['textimage_info'] = array (
   '#type' => 'item',
   '#value' => $imagefontinfo,
  );
  
  $form['mode'] = array(
    '#type' => 'fieldset',
    '#title' => t('Mode'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE
  );
  
  $form['mode']['textimage_mode'] = array (
    '#type' => 'select',
    '#title' => t('Mode'),
    '#options' => $modes,
    '#default_value' => variable_get('textimage_mode', 'normal'),
  );

  //Fonts settings
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Text image settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE
  );

  $form['settings']['textimage_fonts_path'] = array(
    '#type' => 'textfield',
    '#title' => t('TrueType Fonts Path'),
    '#default_value' => variable_get('textimage_fonts_path', ''),
    
    '#maxlength' => 255,
    '#description' => t('Location of the directory where the Truetype (.ttf) fonts are stored. If you do not provide any fonts, the module will use the default font for text. Relative paths will be resolved relative to the Drupal installation directory.'),
  );

  $form['settings']['textimage_images_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Background Image Path'),
    '#default_value' => variable_get('textimage_images_path', ''),
    
    '#maxlength' => 255,
    '#description' => t('Location of the directory where the background images are stored. Images in this directory can be overlaid with dynamic text in a preset. Relative paths will be resolved relative to the Drupal installation directory.'),
  );

  $form['settings']['textimage_security_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Security key'),
    '#default_value' => variable_get('textimage_security_key', ''),
    '#size' => 30,
    '#maxlength' => 50,
    '#description' => t('This key is used to verify text image requests. Use this to protect your server from DoS.'),
  );
  
  $form['#submit'] = array(
  		'system_settings_form_submit' => array(),
  		'textimage_settings_form_postprocess' => array(), 
  );
  
  return system_settings_form($form);
}

function textimage_settings_form_postprocess($form,$form_values) {
	cache_clear_all('*', 'cache_filter', TRUE);
	// The cache_content table is not created during installation. So we need to check if it has already been created some other time.
	if(db_table_exists('cache_content')) {
		cache_clear_all('*', 'cache_content', TRUE);
	}
}

function textimage_settings_form_validate($form_id, $form_values) {
  //check for valid font path
  if (!empty($form_values['textimage_fonts_path']) && !is_dir(rtrim($form_values['textimage_fonts_path'], '\\/')))
  form_set_error('textimage_fonts_path', t('The entered font path is invalid'));

  //check for valid image path
  if (!empty($form_values['textimage_images_path']) && !is_dir(rtrim($form_values['textimage_images_path'], '\\/')))
  form_set_error('textimage_images_path', t('The entered image path is invalid'));

  //if mode is set to Secure we need to make sure a password is provided
  if ($form_values['textimage_mode'] == 'secure' && empty($form_values['textimage_security_key']))
  form_set_error('textimage_security_key', t('You have to enter a security key if you use Security mode'));
}

function textimage_preset_edit($preset_id) {
  if (is_numeric($preset_id)) {
    $preset = _textimage_preset_load($preset_id);
  }
  else {
    $preset = array();
  }
  
  $fonts_path = variable_get('textimage_fonts_path', '');
  $font_options = drupal_map_assoc(_textimage_font_list($fonts_path));
  
  $images_path = variable_get('textimage_images_path', '');
  $image_files = drupal_map_assoc(_textimage_image_list_short($images_path));
  $image_files_with_default = $image_files;
  $image_files_with_default['default'] = t('Default (Use Text Color)');
  
  $image_presets = array();
  $presets = textimage_get_presets();
  foreach ($presets as $p) {
    if ($p['pid'] != $preset_id) {
      $image_presets[$p['pid']] = $p['name'];
    }
  }
  
  $image_options = array(
    t('Default:') => array('' => t('Default (use background color)')),
    t('Use the result of Text Image Preset:') => $image_presets,
    t('Use a Background Image:') => $image_files,
  );

  $form = array();
  $form['#tree'] = TRUE;
  
  $form['preset_id'] = array(
    '#type' => 'value',
    '#value' => $preset_id,
  );
  
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Preset Name'),
    '#description' => t('Preset names should be short and only use alphanumeric characters. This name will be used in the image path for all generated images.'),
    '#default_value' => $preset['name'],
    '#required' => TRUE,
  );
  
  $form['settings']['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('A short description displayed in the list of presets.'),
    '#default_value' => $preset['settings']['description'],
    '#rows' => 1,
  );
  
  $form['settings']['text'] = array(
    '#type' => 'fieldset',
    '#title' => t('Text Settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['settings']['text']['font'] = array(
    '#type' => 'select',
    '#title' => t('Font'),
    '#options' => $font_options,
    '#default_value' => $preset['settings']['text']['font'],
    '#description' => t('Select the font to be used in this image. If no fonts are listed, check the <a href="!path">settings for TrueType Fonts Path</a>.', array('!path' => url('admin/settings/textimage'))),
    '#required' => TRUE,
  );
  $form['settings']['text']['size'] = array(
    '#type' => 'textfield',
    '#title' => t('Font Size'),
    '#description' => t('Enter the size in pixels of the text to be generated.'),
    '#default_value' => $preset['settings']['text']['size'] ? $preset['settings']['text']['size'] : 16,
    '#maxlength' => 4,
    '#size' => 4,
    '#required' => TRUE,
    '#validate' => array('_textimage_number_validate' => array('size')),
  );

  $form['settings']['text']['color'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Color'),
    '#description' => t('Enter the hex color code you wish to use for the generated text (i.e. #000000).'),
    '#default_value' => $preset['settings']['text']['color'],
    '#maxlength' => 7,
    '#size' => 7,
    '#validate' => array('_textimage_hex_validate' => array('color')),
    '#required' => TRUE,
  );
  $form['settings']['text']['maximum_width'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum width'),
    '#field_suffix' => t('pixels'),
    '#description' => t('Text lines wider than this will be wrapped. Leave blank to disable wrapping.'),
    '#default_value' => $preset['settings']['text']['maximum_width'],
    '#maxlength' => 4,
    '#size' => 4,
  );
  $form['settings']['text']['fixed_width'] = array(
    '#type' => 'checkbox',
    '#title' => t('Fixed width?'),
    '#description' => t('If checked the size of generated image will always be equal to the max width.'),
    '#default_value' => $preset['settings']['text']['fixed_width'],
  );
  $form['settings']['text']['align'] = array(
    '#type' => 'select',
    '#title' => t('Text Align'),
    '#description' => t('Only works when fixed width is enabled.'),
    '#options' => array(ALIGN_LEFT => t('Left'), ALIGN_CENTER => t('Center'), ALIGN_RIGHT => t('Right')),
    '#default_value' => $preset['settings']['text']['align'],
  );
  $form['settings']['text']['angle'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Rotation'),
    '#description' => t('Enter the angle in degrees at which the text will be displayed. Positive numbers rotate the text clockwise, negative numbers counter-clockwise.'),
    '#default_value' => $preset['settings']['text']['angle'] ? $preset['settings']['text']['angle'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('angle')),
  );
  $form['settings']['text']['case'] = array(
    '#type' => 'radios',
    '#title' => t('Convert to case'),
    '#options' => array('' => t('Default'), 'upper' => t('UPPERCASE'), 'lower' => t('lowercase'), 'ucwords' => t('Uppercase Words'), 'ucfirst' => t('Uppercase first')),
    '#description' => t('Covert the input text to a consistent format. The default makes no changes to input text.'),
    '#default_value' => $preset['settings']['text']['case'],
  );
  $form['settings']['text']['stroke_width'] = array(
    '#type' => 'textfield',
    '#title' => t('Outline Width'),
    '#description' => t('Optionally add a stroke outline around the text. Enter the stroke width in pixels.'),
    '#default_value' => $preset['settings']['text']['stroke_width'],
    '#maxlength' => 1,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('stroke_width')),
  );
  $form['settings']['text']['stroke_color'] = array(
    '#type' => 'textfield',
    '#title' => t('Outline Color'),
    '#description' => t('Enter the hex color code you wish to use for the stroke outline (i.e. #000000).'),
    '#default_value' => $preset['settings']['text']['stroke_color'],
    '#maxlength' => 7,
    '#size' => 7,
    '#validate' => array('_textimage_hex_validate' => array('color')),
  );
  $form['settings']['text']['margin_top'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Margin Top'),
    '#default_value' => $preset['settings']['text']['margin_top'] ? $preset['settings']['text']['margin_top'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('margin_top')),
  );
  $form['settings']['text']['margin_right'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Margin Right'),
    '#default_value' => $preset['settings']['text']['margin_right'] ? $preset['settings']['text']['margin_right'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('margin_top')),
  );
  $form['settings']['text']['margin_bottom'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Margin Bottom'),
    '#default_value' => $preset['settings']['text']['margin_bottom'] ? $preset['settings']['text']['margin_bottom'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('margin_bottom')),
  );
  $form['settings']['text']['margin_left'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Margin Left'),
    '#description' => t('Specify the margin in pixels to be added around the generated text.'),
    '#default_value' => $preset['settings']['text']['margin_left'] ? $preset['settings']['text']['margin_left'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('margin_left')),
  );
  
  $form['settings']['background'] = array(
    '#type' => 'fieldset',
    '#title' => t('Background'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['settings']['background']['color'] = array(
    '#type' => 'textfield',
    '#title' => t('Background Color'),
    '#description' => t('Enter the hex color code you wish to use for the background of the generated image (i.e. #FFFFFF). Leave blank for transparent.'),
    '#default_value' => $preset['settings']['background']['color'],
    '#maxlength' => 7,
    '#size' => 7,
    '#validate' => array('_textimage_hex_validate' => array('color')),
  );
  $form['settings']['background']['image'] = array(
    '#type' => 'select',
    '#title' => t('Background Image'),
    '#options' => $image_options,
    '#default_value' => $preset['settings']['background']['image'],
    '#description' => t('Select the font to be used in this image. If no fonts are listed, check the <a href="!path">settings for TrueType Fonts Path</a>.', array('!path' => url('admin/settings/textimage'))),
  );
  $form['settings']['background']['xoffset'] = array(
    '#type' => 'textfield',
    '#title' => t('Text X-Offset'),
    '#default_value' => $preset['settings']['background']['xoffset'] ? $preset['settings']['background']['xoffset'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('xoffset')),
  );
  $form['settings']['background']['yoffset'] = array(
    '#type' => 'textfield',
    '#title' => t('Text Y-Offset'),
    '#description' => t('Specify the x and y coordinates on the image you where the top-left corner of the dynamic text should be positioned.'),
    '#default_value' => $preset['settings']['background']['yoffset'] ? $preset['settings']['background']['yoffset'] : 0,
    '#maxlength' => 4,
    '#size' => 4,
    '#validate' => array('_textimage_number_validate' => array('yoffset')),
  );
  
  $form['settings']['mask'] = array(
    '#type' => 'fieldset',
    '#title' => t('Mask'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['settings']['mask']['image'] = array(
    '#type' => 'select',
    '#title' => t('Mask Image'),
    '#options' => $image_files_with_default,
    '#default_value' => ($preset['settings']['mask']['image'] ? $preset['settings']['mask']['image'] : 'default'),    
  );
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  
  return $form;
}

function textimage_dir_edit($preset_id) {
  $form = array();
  if (is_numeric($preset_id)) {
    $preset = _textimage_dir_load($preset_id);
  }
  else {
    $preset = array();
  }
  
  $presets = textimage_get_presets();
//  if (count($presets) == 0) {
//  	drupal_set_message(t('No presets defined. <a href="!url">Create a new preset</a>.', array('!url' => url('admin/settings/textimage/new'))), 'status');
//  	return $form;
//  }
  $preset_names = array();
  foreach($presets as $key => $value) {
  	$preset_names[$value['pid']] = $key;
  }
  
  $form['#tree'] = TRUE;
  
  $form['did'] = array(
    '#type' => 'value',
    '#value' => $preset_id,
  );
  $form['selector'] = array(
    '#type' => 'textfield',
    '#title' => t('Selector'),
    '#default_value' => $preset['selector'],
    '#maxlength' => 255,
    '#size' => 30,
    '#required' => TRUE,
  );
  
  $form['preset_id'] = array(
    '#type' => 'select',
    '#title' => t('Preset'),
    '#options' => $preset_names,
    '#default_value' => $preset['preset_id'],
    '#description' => t('If no presets are listed, create one here.'),
    '#required' => TRUE,
  );
  
  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $preset['weight'] ? $preset['weight'] : 0,
    '#required' => TRUE,
  );
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  
  return $form;
}

function textimage_preset_edit_validate($form_id, $form_values) {
  // Check for illegal characters in preset names
  if (preg_match('/[^0-9a-zA-Z_\-]/',$form_values['name'])) {
    form_set_error('name',t('Please only use alphanumic characters, underscores (_), and hyphens (-) for preset names.'));
  }
  
  if ($form_values['preset_id'] == 'new') {
    // Check for duplicate preset names
    $preset = _textimage_preset_load_by_name($form_values['name']);
    if ($preset['name']) {
      form_set_error('name', t('The name %name is already in use by another preset.', array('%name' => $form_values['name'])));
    }
  }
}

function textimage_preset_edit_submit($form_id, $form_values) {
  if ($form_values['preset_id'] == 'new') {
    $return = _textimage_preset_create($form_values['name'], $form_values['settings']);
  }
  elseif (is_numeric($form_values['preset_id'])) {
    $return = _textimage_preset_update($form_values['preset_id'], $form_values['name'], $form_values['settings']);
  }
  
  if ($return) {
    drupal_set_message(t('Updated preset %name', array('%name' => $form_values['name'])));
    drupal_goto('admin/settings/textimage/presets/list');
  }
  else {
    drupal_set_message(t('The preset was unable to be updated.', 'error'));
  }
}

function textimage_dir_edit_submit($form_id, $form_values) {
  if ($form_values['did'] == 'new') {
    $return = _textimage_dir_create($form_values['selector'], $form_values['preset_id'], $form_values['weight']);
  }
  elseif (is_numeric($form_values['did'])) {
    $return = _textimage_dir_update($form_values['did'], $form_values['selector'], $form_values['preset_id'], $form_values['weight']);
  }
  
  if ($return) {
    drupal_set_message(t('Updated preset %selector', array('%selector' => $form_values['selector'])));
    drupal_goto('admin/settings/textimage/dir/list');
  }
  else {
    drupal_set_message(t('The preset was unable to be updated.', 'error'));
  }
}

function textimage_preset_list() {
  $presets = textimage_get_presets();
  $header = array(t('Name'), t('Summary'), t('Description'), array('data' => t('Operations'), 'colspan' => '2'));
  $rows = array();

  foreach ($presets as $name => $preset) {
    $row = array();
    $row[] = $name;
    $row[] = $preset['settings']['text']['font'] . ' ' . $preset['settings']['text']['size'] . t('px');
    $row[] = $preset['settings']['description'];
    $row[] = l(t('edit'), 'admin/settings/textimage/presets/edit/'. $preset['pid']);
    $row[] = l(t('delete'), 'admin/settings/textimage/presets/delete/'. $preset['pid']);
    $row[] = l(t('flush cache'), 'admin/settings/textimage/presets/flush/'. $preset['pid']);
  
    $rows[] = $row;
  }

  if (empty($rows)) {
    $rows[] = array(array('data' => t('No presets defined. <a href="!url">Create a new preset</a>.', array('!url' => url('admin/settings/textimage/presets/new'))), 'colspan' => '5', 'class' => 'message'));
  }

  return theme('table', $header, $rows);
}

function textimage_dir_list() {
  $presets = textimage_get_dir();
  $header = array(t('Selector'), t('Preset'), t('Weight'), array('data' => t('Operations'), 'colspan' => '2'));
  $rows = array();

  foreach ($presets as $id => $preset) {
    $row = array();
    $row[] = $preset['selector'];
    $row[] = $preset['preset_name'];
    $row[] = $preset['weight'];
//    $row[] = $preset['settings']['text']['font'] . ' ' . $preset['settings']['text']['size'] . t('px');
//    $row[] = $preset['settings']['description'];
    $row[] = l(t('edit'), 'admin/settings/textimage/dir/edit/'. $preset['did']);
    $row[] = l(t('delete'), 'admin/settings/textimage/dir/delete/'. $preset['did']);
  
    $rows[] = $row;
  }

  if (empty($rows)) {
    $rows[] = array(array('data' => t('No presets defined. <a href="!url">Create a new preset</a>.', array('!url' => url('admin/settings/textimage/dir/new'))), 'colspan' => '4', 'class' => 'message'));
  }

  return theme('table', $header, $rows);
}

function textimage_preset_delete_confirm($preset_id) {
  $preset = _textimage_preset_load($preset_id);
  
  $form['pid'] = array('#type' => 'value', '#value' => $preset_id);
  $form['name'] = array('#type' => 'value', '#value' => $preset['name']);
  
  return confirm_form($form,
    t('Are you sure you want to delete the preset %name?', array('%name' => $preset['name'])),
    $_GET['destination'] ? $_GET['destination'] : 'admin/settings/textimage/presets/list',
    t('This action cannot be undone.'),
    t('Delete'), t('Cancel')
 );
}

function textimage_dir_delete_confirm($preset_id) {
  $preset = _textimage_dir_load($preset_id);
  
  $form['did'] = array('#type' => 'value', '#value' => $preset_id);
  $form['selector'] = array('#type' => 'value', '#value' => $preset['selector']);
  
  return confirm_form($form,
    t('Are you sure you want to delete the preset %selector?', array('%selector' => $preset['selector'])),
    $_GET['destination'] ? $_GET['destination'] : 'admin/settings/textimage/dir/list',
    t('This action cannot be undone.'),
    t('Delete'), t('Cancel')
 );
}

function textimage_preset_delete_confirm_submit($form_id, $form_values) {
  _textimage_preset_delete($form_values['pid']);
  drupal_set_message(t('Deleted preset %name', array('%name' => $form_values['name'])));
  drupal_goto('admin/settings/textimage/presets/list');
}

function textimage_dir_delete_confirm_submit($form_id, $form_values) {
  _textimage_dir_delete($form_values['did']);
  drupal_set_message(t('Deleted preset %selector', array('%selector' => $form_values['selector'])));
  drupal_goto('admin/settings/textimage/dir/list');
}

function textimage_preset_flush_confirm($preset_id) {
  $preset = _textimage_preset_load($preset_id);
  
  $form['pid'] = array('#type' => 'value', '#value' => $preset_id);
  $form['name'] = array('#type' => 'value', '#value' => $preset['name']);
  
  return confirm_form($form,
    t('Are you sure you want to flush the image cache for preset %name?', array('%name' => $preset['name'])),
    $_GET['destination'] ? $_GET['destination'] : 'admin/settings/textimage/presets/list',
    t('This action cannot be undone.'),
    t('Flush Cache'), t('Cancel')
 );
}

function textimage_preset_flush_confirm_submit($form_id, $form_values) {
  _textimage_preset_flush($form_values['pid']);
  drupal_goto('admin/settings/textimage/list');
}

function textimage_get_presets() {
  $result = db_query('SELECT pid, name, settings FROM {textimage_preset}');
  $presets = array();
  while ($preset = db_fetch_array($result)) {
    $preset['settings'] = unserialize($preset['settings']);
    $presets[$preset['name']] = $preset;
  }
  return $presets;
}

function textimage_get_dir() {
  $result = db_query('SELECT did, selector, preset_id, p.name as preset_name, weight FROM {textimage_dir} AS d INNER JOIN {textimage_preset} as p WHERE d.preset_id = p.pid ORDER BY weight ASC');
  $presets = array();
  while ($preset = db_fetch_array($result)) {
    $presets[] = $preset;
  }
  return $presets;
}

/**
 * Menu Callback function converts the current textimage path into an image. On
 * first request, the image is returned to the browser from Drupal and then
 * saved to the disk. On subsequent requests (with Clean URLs enabled), the
 * cached image is loaded directly.
 * 
 * This function takes a dynamic number of arguments.
 * 
 * @param $preset_name
 *   The name of the preset to be used in this text image
 * @param ...
 *   An unlimited number of additional text parameters to be used as the
 *   display text for text images displayed on top of one another. Only used
 *   if the current preset has the its Background Image option set to the
 *   result of another preset. Text is used in reverse order. So the last
 *   directory will be the first chained preset used.
 * @param $text
 *   The text to be displayed in this preset with the output format
 *   appended as the file extension. For example, 'sample.png' will output a
 *   PNG with the text 'sample'. 'sample.jpg' will output the same image but
 *   in JPG format.
 * 
 */
function textimage_image() {
  $args = func_get_args();
  
  $preset_name = array_shift($args);
  $filename = array_pop($args);
  $additional_text = $args; 
  
  
  // Determine our output format
  preg_match('/\.([a-z]+)$/i', $filename, $matches);
  $format = $matches[1];
  if ($format == 'jpg') {
    $format = 'jpeg';
  }
  
  // Determine the text to display
  $text = preg_replace('/\.([a-z]+)$/i', '', $filename);
  
  if(variable_get('textimage_mode', 'normal') == 'secure') {
  	// ADDED BY DAVY FOR SECURITY TESTING
  	preg_match('/^[^-]+/i', $text, $code);

  	$text = substr($text, strlen($code[0]) + 1, strlen($text) - strlen($code[0]));

  	if($code[0] != md5(variable_get('textimage_security_key', '') . ':' . $text)) {
  		exit();
  	}
  }
 
  // Integrety check
  $output_function = 'image' . $format;
  if (!function_exists($output_function)) {
    $message = t('Unable to generate Text Image %text because the file extension is unsupported on this system. Files must have a .png, .jpg, or .gif extension.', array('%text' => $filename));
    watchdog('textimage', $message, WATCHDOG_ERROR);
    return $message;
  }
  
  // Generate the image resource
  $preset = _textimage_preset_load_by_name($preset_name);
  // Check that it exists
  if (!$preset) {
    $message = t('Unable to generate Text Image %text because the preset %preset is not defined.', array('%text' => $filename, '%preset' => $preset_name));
    watchdog('textimage', $message, WATCHDOG_ERROR);
    return $message;
  }
  
  $img = textimage_image_from_preset($preset['pid'], $text, $additional_text);
  
  // Display the image to be browser
  header('Content-type: image/'.$format);
  $output_function($img);
  
  // Save the result so we don't have to recreate
  textimage_directory_check(str_replace('/'. $filename, '', $_GET['q']));
  $output_function($img, $_GET['q']);
  imagedestroy($img);
  exit();
}

/**
 * Loads the Text Image preset and generates the GD image resource.
 * 
 * @param $preset_id
 *   The id of the preset to be used in this text image
 * @param $text
 *   The text to be displayed in this preset
 * @param $additional_text
 *   An array of text to be used in subsequent text images. Only used if this
 *   preset uses the result of another preset as its background image.
 */
function textimage_image_from_preset($preset_id, $text, $additional_text) {
  $preset = _textimage_preset_load($preset_id);
  $text = str_replace('_', ' ', $text);

  $font = variable_get('textimage_fonts_path', '') . '/' . $preset['settings']['text']['font'];

  // Fill in default preset settings.
  $defaults = array(
    'text' => array(
      'size' => '',
      'angle' => 0,
      'color' => '',
      'case' => '',
      'stroke_width' => '',
      'stroke_color' => '#000000',
      'maximum_width' => 0,
      'margin_top' => 0,
      'margin_right' => 0,
      'margin_bottom' => 0,
      'margin_left' => 0,
      'fixed_width' => 0,
      'align' => ALIGN_LEFT,
    ),
    'background' => array(
      'back_color' => '',
      'back_image' => '',
      'back_xoffset' => 0,
      'back_yoffset' => 0,
    ),
    'mask' => array(
      'mask_image' => '',
    ),
  );
  foreach ($defaults as $set => $values) {
    $settings = $preset['settings'][$set];
    foreach ($values as $key => $value) {
      // Strip prefixes like 'back_' for array lookups.
      $array_key = preg_replace('/^back_/', '', $key);
      $array_key = preg_replace('/^mask_/', '', $array_key);
      // Assign to variable and fill in default.
      $$key = $settings[$array_key] ? $settings[$array_key] : $value;
    }
  }
  
  $images_path = variable_get('textimage_images_path', '');
  
  if(isset($mask_image) && !empty($mask_image) && $mask_image != 'default') {
  	$mask_image = $images_path . '/' . $mask_image;
  }
  if(isset($back_image) && !empty($back_image)) {
  	$back_image = $images_path . '/' . $back_image;
  }

  // Convert text case
  switch ($case) {
    case 'upper':
      $text = drupal_strtoupper($text);
      break;
    case 'lower':
      $text = drupal_strtolower($text);
      break;
    case 'ucfirst':
      $text = drupal_ucfirst($text);
      break;
    case 'ucwords':
      $text = drupal_strtolower($text);
      $words = explode(' ', $text);
      foreach ($words as $key => $word) {
        $words[$key] = drupal_ucfirst($word);
      }
      $text = implode(' ', $words);
      break;
    
  }
  if (function_exists('drupal_' . $case)) {
  	$text = drupal_strtoupper($text);
  }
  
  if(empty($maximum_width)) {
  	$maximum_width = textimage_measure_text_width($text, $size, $font);
  }

  $mask = textimage_text_to_image($text, $size, $font, $color, NULL, $angle, $maximum_width, $fixed_width, $align);
  if($mask_image != 'default') {
  	// Generate the text image

  	//$img_tmp = imagecreatetruecolor(imagesx($mask), imagesy($mask));

  	//imagecopy($img_tmp, $img, 0,0,0,0, imagesx($img_tmp), imagesy($img_tmp));
  	//imagedestroy($img);
  	//$img = imagecreatetruecolor(imagesx($img_tmp), imagesy($img_tmp));
  	$img;
  	//$gradient = imagecreatefrompng($mask_image);
  	
  	    $gradient_info = image_get_info($mask_image);
    $gradient = image_gd_open($mask_image, $gradient_info['extension']);
	
  	
  	_textimage_mask($img, $gradient, '', NULL, $mask, NULL);

  	imagedestroy($mask);
  	imagedestroy($gradient);
  } else {
  	$img = $mask;
  }
	
  // Add margin
  if ($margin_top || $margin_right || $margin_bottom || $margin_left) {
    $img = textimage_image_add_margin($img, $margin_top, $margin_right, $margin_bottom, $margin_left, $back_color);
  }

  // Add a border
  if ($stroke_width && $stroke_color) {
    $img = textimage_image_add_stroke($img, $stroke_width, $stroke_color);
  }
  
  // Place result on top of another preset's result
  if (is_numeric($back_image) && _textimage_preset_load($back_image)) {
    $next_preset = $back_image;
    $next_text = array_pop($additional_text);
    $background_resource = textimage_image_from_preset($next_preset, $next_text, $additional_text);
    
    $text_width = imagesx($img);
    $text_height = imagesy($img);
    imagealphablending($background_resource, true);
    imagecopy($background_resource, $img, $back_xoffset, $back_yoffset, 0, 0, $text_width,  $text_height);
    $img = $background_resource;
  }
  // Place result on background image if available
  elseif (is_file($back_image)) {
    $info = image_get_info($back_image);
    $background_resource = image_gd_open($back_image, $info['extension']);
    
    $text_width = imagesx($img);
    $text_height = imagesy($img);
    imagecopy($background_resource, $img, $back_xoffset, $back_yoffset, 0, 0, $text_width,  $text_height);
    $img = $background_resource;
  }
  
  return $img;
}

/**
 * This function adds a margin (or border) around an existing image resource
 */
function textimage_image_add_margin($img, $top, $right, $bottom, $left, $background_color = NULL) {
  $iw = imagesx($img);
  $ih = imagesy($img);

  // Create a new image for the background
  if (empty($background_color)) {
    $back_img = _textimage_create_transparent_image($iw+$right+$left, $ih+$top+$bottom);
  }
  else {
    $back_img = imagecreatetruecolor($iw+$right+$left, $ih+$top+$bottom);
    list($r, $g, $b) = _textimage_hex2rgb($background_color);
    $back = imagecolorallocate($back_img, $r, $g, $b);
    imagefill($back_img, 0, 0, $back);
  }
  
  // Apply the source image ontop the background with the new margin
  imagecopy($back_img, $img, $left, $top, 0, 0, $iw,  $ih);
  return $back_img;
}

/**
 * Stroke function adds a solid color stroke around an image with a transparent
 * background.
 * 
 * @param $img
 *   The gd image resource of the image to modify
 * @param $thickness
 *   The width of the stroke to apply
 * @param $color
 *   The color of the stroke to apply
 * 
 * @todo Add $position parameter to allow the stroke to be applied 'inside',
 * 'middle', or 'outside'. outside is the only current behavior.
 */
function textimage_image_add_stroke($img, $thickness, $color) {
  if ($thickness > 0) {
    $width  = imagesx($img);
    $height = imagesy($img);
  
    // Create a new image which we'll lay over the original
    $border_img = _textimage_create_transparent_image($width, $height);
    for ($x=0; $x < $width; $x++) {
      for ($y=0; $y < $height; $y++) {
        $c = imagecolorsforindex($img, imagecolorat($img, $x, $y ));
        // Outside only modify pixels which are not 100% opaque
        if ($c['alpha'] > 0) {
          textimage_image_stroke_change_pixels($img, $border_img, $thickness, $color, $x, $y, $width, $height);
        }
      }
    }
    // Merge the images
    imagealphablending($img, true);
    imagecopy($img, $border_img, 0, 0, 0, 0, $width,  $height);
  }
  
  return $img;
}

/**
 * Utility function for image_stroke. Analyzes surrounding pixels and determines
 * opacity of a pixel at that x-y coordinate
 */
function textimage_image_stroke_change_pixels(&$img, &$border_img, $thickness, $color, $x, $y, $width, $height) {
  list($r, $g, $b) = _textimage_hex2rgb($color);

  $pixel = imagecolorsforindex($img, imagecolorat($img, $x, $y));
  
  // Preform a radial analysis of all pixels within the radius of $thickness pixels
  $degree_increment = (90/$thickness);
  $radial_coords = array();
  for ($degrees = 0; $degrees <= 90; $degrees += $degree_increment) {
    $x_offset = round(cos($degrees) * $thickness);
    $y_offset = round(sin($degrees) * $thickness);
    
    // Add the coordinates for the corresponding pixel in each 90° quadrant
    $radial_coords[] = array('x' => $x + $x_offset, 'y' => $y + $y_offset);
    $radial_coords[] = array('x' => $x - $x_offset, 'y' => $y + $y_offset);
    $radial_coords[] = array('x' => $x + $x_offset, 'y' => $y - $y_offset);
    $radial_coords[] = array('x' => $x - $x_offset, 'y' => $y - $y_offset);
  }
  
  // Generate a total alpha level for all analyzed pixels
  $total_alpha = 0;
  $total_colors = 0;
  foreach($radial_coords as $coords) {
    if ($coords['x'] >= 0 && $coords['y'] >= 0 && $coords['x'] < $width && $coords['y'] < $height) {
       $xy_color = imagecolorsforindex($img, imagecolorat($img, $coords['x'], $coords['y']));
    }
    else {
      // This analized pixel is outside the dimensions of the image, record as transparent
      $xy_color = array('alpha' => '127');
    }
    $total_alpha += $xy_color['alpha'];
    $total_colors++;
  }
  
  // Check that we're not in the middle of the image or in a blonk area
  if ($total_alpha < (127 * $total_colors) && $total_alpha > 0) {
    // If we're on a semi-transparent pixel, blend the remaining amount with our border color
    if ($pixel['alpha'] < 127) {
      $alpha = 127 - $pixel['alpha'];
    }
    // We're on a completely transparent pixel where we'll use a generated transparency
    else {
      $alpha = 127 - ((127 * $total_colors) - $total_alpha);
    }
    $alpha = ($alpha < 0) ? 0 : $alpha;
    $alpha = ($alpha > 127) ? 127 : $alpha;
    // Apply the color to the border overlay image
    $color = imagecolorallocatealpha($border_img, $r, $g, $b, $alpha);
    imagesetpixel($border_img, $x, $y, $color);
  }
}

/**
 * Create the image directory relative to the 'files' dir - if user specified one
 * Won't allow form submit unless the directory exists & is writable
 * 
 * @param $directory_path
 *   String containing the path of the directory to check.
 */ 
function textimage_directory_check($directory_path) {
  // create each directory necessary if it doesn't exist
  foreach(explode('/', $directory_path) as $dir) {
    $dirs[] = $dir;
    if (!file_check_directory(implode($dirs,'/'), FILE_CREATE_DIRECTORY)) {
      return false;
    }
  }
  return true;
}

/**
 * Helper function for wrapping text (measures width).
 */
function textimage_measure_text_width($text, $fontsize, $font) {
  $box = imageTTFBbox($fontsize, $angle, $font, $text);
  return abs($box[4] - $box[0]) + 4;
}

/**
 * Wrap text for rendering at a given width.
 */
function textimage_wrap_text($text, $fontsize, $font, $maximum_width) {
  // State variables for the search interval
  $end = 0;
  $begin = 0;
  $fit = $begin;

  // Note: we count in bytes for speed reasons, but maintain character boundaries.
  while (true) {
    // Find the next wrap point (always after trailing whitespace).
    if (preg_match('/['. PREG_CLASS_PUNCTUATION .']['. PREG_CLASS_SEPARATOR .']*|['. PREG_CLASS_SEPARATOR .']+/u', $text, $match, PREG_OFFSET_CAPTURE, $end)) {
      $end = $match[0][1] + strlen($match[0][0]);
    }
    else {
      $end = strlen($text);
    }
    
    // Fetch text, removing trailing white-space and measure it.
    $line = preg_replace('/['. PREG_CLASS_SEPARATOR .']+$/u', '', substr($text, $begin, $end - $begin));
    $width = textimage_measure_text_width($line, $fontsize, $font);

    // See if $line extends past the available space.
    if ($width > $maximum_width) {
      // If this is the first word, we need to truncate it.
      if ($fit == $begin) {
        // Cut off letters until it fits.
        while (strlen($line) > 0 && $width > $maximum_width) {
          $line = drupal_substr($line, 0, -1);
          $width = textimage_measure_text_width($line, $fontsize, $font);
        }
        // If no fit was found, the image is too narrow..
        $fit = strlen($line) ? $begin + strlen($line) : $end;
      }

      // We have a valid fit for the next line. Insert a line-break and reset
      // the search interval.
      $text = substr($text, 0, $fit) ."\n". substr($text, $fit);
      $end = $begin = ++$fit;
    }
    else {
      // We can fit this text. Wait for now.
      $fit = $end;
    }

    if ($end == strlen($text)) {
      // All text fits. No more changes are needed.
      break;
    }
  }
  return $text;
}

/**
 * Generate an image containing text with the given parameters.
 *
 * @return $image
 *   A GD image resource.
 */
function textimage_text_to_image($text, $fontsize, $font, $foreground_color = '#000000', $background_color = NULL, $angle = 0, $maximum_width = 0, $fixed_width = 0, $align = ALIGN_LEFT) {
  // Perform text wrapping, if necessary.
  if ($maximum_width > 0) {
    $text = textimage_wrap_text($text, $fontsize, $font, $maximum_width);
  }

  // Get exact dimensions of text string
  $box = imageTTFBbox($fontsize, $angle, $font, $text);
  // Calculate text width and height
  $iw = abs($box[4] - $box[0]) + 4;
  $ih = abs($box[5] - $box[1]) + 4;
  
  if($fixed_width) {
    $background_width = $maximum_width;
  }
  else {
    $background_width = $iw;
  }

  // Use a transparent background color
  if (empty($background_color)) {
    $img = _textimage_create_transparent_image($background_width, $ih);
  }
  // Use a solid background color
  else {
    $img = imagecreatetruecolor($background_width, $ih);
    list($r, $g, $b) = _textimage_hex2rgb($background_color);
    $back = imagecolorallocate($img, $r, $g, $b);
    imagefill($img, 0, 0, $back);
  }
  
  // Create the text image
  list($r, $g, $b) = _textimage_hex2rgb($foreground_color);
  $fore = imagecolorallocate($img, $r, $g, $b);

  switch($align) {
    case ALIGN_RIGHT:
      $x = $maximum_width - $iw;
      break;
    case ALIGN_CENTER:
      $x = ($maximum_width - $iw) / 2;
      break;
    case ALIGN_LEFT:
    default:
      $x = 0;
      break;
  }

  imagettftext($img, $fontsize, $angle, $x, abs($box[5]), $fore, $font, $text);
  
  return $img;
}

/** 
 * load a preset by id.
 * @param id
 *    preset id.
 */
function _textimage_preset_load($id) {
  $result = db_query('SELECT pid, name, settings FROM {textimage_preset} WHERE pid = %d', $id);
  $preset = db_fetch_array($result);
  
  if ($preset['pid']) {
    $preset['settings'] = unserialize($preset['settings']);
    return $preset;
  }
  else {
    return false;
  }
}

function _textimage_dir_load($id) {
  $result = db_query('SELECT did, selector, preset_id, p.name as preset_name, weight FROM {textimage_dir} AS d INNER JOIN {textimage_preset} as p WHERE d.preset_id = p.pid AND did = %d', $id);
  $preset = db_fetch_array($result);
  if ($preset['did']) {
    return $preset;
  }
  else {
    return false;
  }
}

/** 
 * load a preset by name
 *  @param name
 *    preset name
 */
function _textimage_preset_load_by_name($name) {
  $result = db_query("SELECT pid, name, settings FROM {textimage_preset} WHERE name = '%s'", $name);
  if (db_num_rows($result) == 0) {
    return false;
  }
  else {
    $preset = db_fetch_array($result);
    $preset['settings'] = unserialize($preset['settings']);
    return $preset;
  }
}

/** 
 * create a preset 
 * @param name
 *    name of the preset to be created.
 */
function _textimage_preset_create($name, $settings) {
  $next_id = db_next_id('{textimage_preset}_pid');
  return db_query('INSERT INTO {textimage_preset} (pid, name, settings) VALUES (%d, \'%s\', \'%s\')', $next_id, $name, serialize($settings));
}

function _textimage_dir_create($selector, $preset_id, $weight) {
  $next_id = db_next_id('{textimage_dir}_did');
  return db_query('INSERT INTO {textimage_dir} (did, selector, preset_id, weight) VALUES (%d, \'%s\', %d, %d)', $next_id, $selector, $preset_id, $weight);
}

/**
 * update a preset
 * @param id
 *    preset id 
 * @param name
 *    new name for the preset
 */
function _textimage_preset_update($id, $name, $settings) {
  $name = check_plain($name);
  _textimage_preset_flush($id);
  return db_query('UPDATE {textimage_preset} SET name =\'%s\', settings =\'%s\' WHERE pid = %d', $name, serialize($settings), $id);
}

function _textimage_dir_update($id, $selector, $preset_id, $weight) {
  return db_query('UPDATE {textimage_dir} SET selector =\'%s\', preset_id = %d, weight = %d WHERE did = %d', $selector, $preset_id, $weight, $id);
}


function _textimage_preset_delete($id) {
  _textimage_preset_flush($id);
  return db_query('DELETE FROM {textimage_preset} where pid = %d', $id);
}


function _textimage_dir_delete($id) {
  return db_query('DELETE FROM {textimage_dir} where did = %d', $id);
}


/**
 * flush cached media for a preset.
 * @param id
 *   a preset id.
 */
function _textimage_preset_flush($id) {
    drupal_set_message(t('Flushed Preset Images (ID: @id)', array('@id' => $id)));
    $preset = _textimage_preset_load($id);
    $presetdir = realpath(file_directory_path() .'/textimage/'. $preset['name']);
    if (is_dir($presetdir)) {
      _textimage_recursive_delete($presetdir);
      @unlink($presetdir);
    }
}


/**
 * Recursively delete all files and folders in the specified filepath, then
 * delete the containing folder Note that this only deletes visible files with
 * write permission
 *
 * @param string $path
 *   An absolute filepath (relative to the filesystem) to delete
 */
function _textimage_recursive_delete($path) {
  $listing = $path . "/*";
  foreach(glob($listing) as $file) {
    if(is_file($file) === true) {
      @unlink($file);
    }
    elseif(is_dir($file) === true) {
      _textimage_recursive_delete($file);
      @rmdir($file);
    }
  }
  @rmdir($path);
}


function _textimage_number_validate($field, $field_name) {
  if (!empty($field['#value']) && !is_numeric($field['#value'])) {
    form_set_error($field_name, t('The value for %field must be a number.', array('%field' => $field['#title'])));
  }
}


function _textimage_hex_validate($field, $field_name) {
  if (!empty($field['#value']) && !preg_match('/^#[0123456789ABCDEF]{1,6}$/i', $field['#value'])) {
    form_set_error($field_name, t('The value for %field must be in a hexidecimal format (i.e. #FFFFFF is white).', array('%field' => $field['#title'])));
  }
}


/**
 * Returns an array of files with ttf extensions in the specified directory.
 *
 * @param $fontdir
 *   Full path of the font directory.
 * @return
 *   Array of font files.
 */
function _textimage_font_list($fontdir) {
  $filelist = array();
  if (is_dir($fontdir) && $handle = opendir($fontdir)) {
    while ($file = readdir($handle)) {
      if (preg_match("/\.ttf$/i",$file) == 1)
        $filelist[] = $file;
    }
    closedir($handle);
  }

  return $filelist;
}

/**
 * Returns an array of files with jpg, png, and gif extensions in the specified directory. Only the path starting from the $imagesdir path is returned.
 *
 * @param $imagesdir
 *   Full path of the images directory.
 * @return
 *   Array of image files.
 */
function _textimage_image_list_short($imagesdir) {
  $filelist = _textimage_image_list($imagesdir);

  for($i = 0; $i < count($filelist); $i++) {
  	$filelist[$i] = substr($filelist[$i], strlen($imagesdir) + 1, strlen($filelist[$i]) - strlen($imagesdir));
  }
  return $filelist;
}

/**
 * Returns an array of files with jpg, png, and gif extensions in the specified directory.
 * 
 * @param $imagesdir
 *   Full path of the images directory.
 * @return
 *   Array of image files.
 */
function _textimage_image_list($imagesdir) {
  $filelist = array();
  if (is_dir($imagesdir) && $handle = opendir($imagesdir)) {
    while ($file = readdir($handle)) {
      if (preg_match("/\.gif|\.png|\.jpg$/i",$file) == 1)
        $filelist[] = $imagesdir.'/'.$file;
    }
    closedir($handle);
  }

  return $filelist;
}


/**
 * Create transparent image resource.
 * 
 * @param $x
 *   Image width.
 * @param $y
 *   Image height.
 * @return
 *   Image identifier of the transparent image resource.
 */
function _textimage_create_transparent_image($x, $y) {
  $i = imagecreatetruecolor($x, $y);
  $b = imagecreatefromstring(base64_decode(_textimage_blankpng()));
  imagealphablending($i, false);
  imagesavealpha($i, true);
  imagecopyresized($i, $b ,0 ,0 ,0 ,0 ,$x, $y, imagesx($b), imagesy($b));
  return $i;
}

/**
 *  Get a string reprenstation of a blank PNG image.
 *
 *  @return
 *    String representing a blank image in PNG format.
 */
function _textimage_blankpng() {
  $c  = "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m";
  $c .= "dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBNCg";
  $c .= "dyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAAN";
  $c .= "egcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQ";
  $c .= "oHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAA";
  $c .= "DXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII=";
  return $c;
}

/**
 *  Convert a hex color representation to it's rgb integer components.
 * 
 *  @param $hex
 *    Hex representation of the color.
 *    Can be in the formats: '#ABC','ABC','#AABBCC','AABBCC'
 *  @return
 *    Array with three components RGB.
 */
function _textimage_hex2rgb($hex) {
  $hex = ltrim($hex,'#');
  if (preg_match('/^[0-9a-f]{3}$/i',$hex)) {
    // 'FA3' is the same as 'FFAA33' so r=FF, g=AA, b=33
    $r = str_repeat($hex{0},2);
    $g = str_repeat($hex{1},2);
    $b = str_repeat($hex{2},2);
  }
  elseif (preg_match('/^[0-9a-f]{6}$/i',$hex)) {
    // #FFAA33 or r=FF, g=AA, b=33
    $r = substr($hex, 0, 2);
    $g = substr($hex, 2, 2);
    $b = substr($hex, 4, 2);
  }

  $r = hexdec($r);
  $g = hexdec($g);
  $b = hexdec($b);
  return array($r, $g, $b);
}


/**
 * Function for creation of text image URLs, automatically replaces spaces with
 * underscores and url encodes for safe use in HTML
 */
function textimage_path($preset, $text, $additional_text = array(), $format = 'png') {
  $additional_text = array_reverse($additional_text);
  $path = file_directory_path() . '/textimage/' . $preset . '/' . (empty($additional_text) ? '' : implode('/', $additional_text) . '/') . $text . '.' . $format;
  return drupal_urlencode(str_replace(' ', '_', $path));
}


/**
 * Theme function for displaying text images
 */
function theme_textimage_image($preset, $text, $additional_text = array(), $format = 'png', $alt = '', $title = '', $attributes = array(), $getsize = TRUE) {
  $path = textimage_path($preset, $text, $additional_text, $format);
  if ($getsize && is_file($path)) {
    list($width, $height, $type, $image_attributes) = @getimagesize($path);
  }
  $attributes = drupal_attributes($attributes);
  return '<img src="'. check_url(base_path() . $path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . $attributes .' />';
}



function _textimage_mask(&$output_obj, $input_obj, $cache_dir, $bg_obj, $origmask_obj, $img_bgcolor) {
	
	$origmask_info_w = imagesx($origmask_obj);
	$origmask_info_h = imagesy($origmask_obj);
	
	$input_obj_w = imagesx($input_obj);
	$input_obj_h = imagesy($input_obj);
	
	$output_obj =  imagecreatetruecolor($origmask_info_w, $origmask_info_h);
	
	for ($i = 0; $i < $origmask_info_w / $input_obj_w; $i++) {
		for ($j = 0; $j < $origmask_info_h / $input_obj_h; $j++) {
			imagecopy(
				$output_obj, $input_obj, 
				$i * imagesx($input_obj), $j * imagesy($input_obj), 
				0, 0, 
				imagesx($input_obj), imagesy($input_obj) 
			);
		}
	}
	
	$image_info_w = $origmask_info_w;
	$image_info_h = $origmask_info_h;
	
	imagealphablending($output_obj, isset($img_bgcolor) || isset($bg_obj));
	
	for ($i = 0; $i < $image_info_w; ++$i) {
		for ($j = 0; $j < $image_info_h; ++$j) {
			$pxl_alpha = imagecolorsforindex(
				$origmask_obj,
				imagecolorat(
					$origmask_obj,
					$i,
					$j
				)
			);
			$pxl_color = imagecolorsforindex(
				$output_obj,
				imagecolorat(
					$output_obj,
					$i,
					$j
				)
			);
			if (isset($img_bgcolor)) {
				$color = imagecolorallocatealpha (
					$output_obj,
					$img_bgcolor['r'],
					$img_bgcolor['g'],
					$img_bgcolor['b'],
					127-$pxl_alpha['alpha']
				);
			}
			else if($bg_obj != NULL) {
				$pxl_bg = imagecolorsforindex(
					$bg_obj,
					imagecolorat(
						$bg_obj,
						$i,
						$j
					)
				);
				$color = imagecolorallocatealpha (
					$output_obj,
					$pxl_bg['red'],
					$pxl_bg['green'],
					$pxl_bg['blue'],
					127 - $pxl_alpha['alpha']
				);
			} else {
				$color = imagecolorallocatealpha (
					$output_obj,
					$pxl_color['red'],
					$pxl_color['green'],
					$pxl_color['blue'],
					$pxl_alpha['alpha']
				);
			}
			imagesetpixel($output_obj, $i, $j, $color);
		}
	}
	imagesavealpha($output_obj, TRUE);
}


function textimage_get_image($text, $preset) {
	// why a key and not a machine key? server farms / use service from other sites
	//$filename = 'files/textimage/' . $preset . '/' . md5(variable_get('textimage_security_key', '') . ':' . $text) . '-' . urlencode($text) . '.png';

	if(variable_get('textimage_mode', 'normal') == 'dynamic' || variable_get('textimage_mode', 'normal') == 'disabled') {
		return $text;
	} else if(variable_get('textimage_mode', 'normal') == 'secure') {
		$filename = md5(variable_get('textimage_security_key', '') . ':' . $text) . '-' . urlencode($text) . '.png';
	} else {
		$filename = urlencode($text) . '.png';
	}
	$filepath = base_path() . file_directory_path() . '/textimage/' . $preset . '/' . $filename;
	return '<img src="' . $filepath . '" alt="' . $text . '" />';
}

/**
 * Implementation of hook_filter().
 */
function textimage_filter($op, $delta = 0, $format = -1, $text = '') {
	switch ($op) {
		case 'list':
			return array(0 => t('TextImage filter'));
		case 'description':
			return t('Use <textimage preset="PresetName">My text</textimage> to specify text that needs to be rendered as an image.');
		case 'settings':
			break;
		case 'no cache':
			return FALSE;
		case 'prepare':
			return $text;
		case 'process':
			return _textimage_replace_tags($text);
		default:
			return $text;
	}
}

function _textimage_replace_tags($text) {
  preg_match('|<textimage\s*preset="([^0-9a-zA-Z_\-]+)">\s*(.*)\s*</textimage>|i', $text, $matches);
  $text = textimage_get_image($matches[2], $matches[1]);

  return $text;
}

function textimage_dir_js() {
	$js_urlencode = "
function URLEncode (clearString) {
	var output = '';
	var x = 0;
	clearString = clearString.toString();
	var regex = /(^[a-zA-Z0-9_.]*)/;
	while (x < clearString.length) {
		var match = regex.exec(clearString.substr(x));
		if (match != null && match.length > 1 && match[1] != '') {
			output += match[1];
			x += match[1].length;
		} else {
			if (clearString[x] == ' ')
			output += '+';
			else {
				var charCode = clearString.charCodeAt(x);
				var hexVal = charCode.toString(16);
				output += '%' + ( hexVal.length < 2 ? '0' : '' ) + hexVal.toUpperCase();
			}
			x++;
		}
	}
	return output;
}
	";
	
	$selector_preset = "
$(document).ready(function() {
	$('%s').each(function(){
		if($(this).html().indexOf('<img src=\"%s/') == -1) {
			$(this).html('<img src=\"%s/' + URLEncode($(this).text()) + '.png\" />');
		}
	});
 });
	";
	
	$presets = textimage_get_dir();

	header("content-type: application/x-javascript");

	print $js_urlencode;

	foreach($presets as $preset) {
	  $base_path = base_path() . file_directory_path() . '/textimage/' . $preset['preset_name'];
	  print sprintf($selector_preset, $preset['selector'], $base_path, $base_path);
	}
}