You are here

textimage.module in Textimage 7.3

Textimage - Provides text to image manipulations.

File

textimage.module
View source
<?php

/**
 * @file
 * Textimage - Provides text to image manipulations.
 */

/**
 * Define constants.
 */
define('TEXTIMAGE_FONTYOURFACE_MIN_VERSION', '7.x-2.6');
define('TEXTIMAGE_MEDIA_MIN_VERSION', '7.x-1.2');
define('TEXTIMAGE_JQUERY_COLORPICKER_MIN_VERSION', '7.x-1.0-rc1');
define('TEXTIMAGE_TOKEN_MIN_VERSION', '7.x-1.5');

/**
 * Hooks implementations.
 */

/**
 * Implements hook_help().
 */
function textimage_help($path, $arg) {
  switch ($path) {
    case 'admin/config/media/textimage':
      $output = '<p>';
      $output .= t('Textimage provides image effects to generate images with overlaid text.') . ' ';
      $output .= t('Use <a href="@image">Image styles</a> features to create Image styles.', array(
        '@image' => url('admin/config/media/image-styles'),
      )) . ' ';
      $output .= t('Adding Textimage effects to a style will identify it as relevant for Textimage.') . ' ';
      $output .= t('On the edit image style form, a "Textimage options" section allows selecting Textimage-specific options for the style.');
      $output .= '</p>';
      return $output;
  }
}

/**
 * Implements hook_theme().
 */
function textimage_theme() {
  $theme = array(
    // Get a textimage from a stored image style.
    'textimage_style_image' => array(
      'variables' => array(
        'style_name' => '',
        'text' => array(),
        'format' => 'png',
        'alt' => 'Image not available.',
        'title' => '',
        'attributes' => array(),
        'caching' => TRUE,
        'node' => NULL,
        'source_image_file' => NULL,
      ),
    ),
    // Get a textimage from a programmatically created image style.
    'textimage_direct_image' => array(
      'variables' => array(
        'effects' => array(),
        'text' => array(),
        'format' => 'png',
        'alt' => 'Image not available.',
        'title' => '',
        'attributes' => array(),
        'caching' => TRUE,
      ),
    ),
    // Format a textimage.
    'textimage_formatter' => array(
      'variables' => array(
        'textimage' => NULL,
        'style_name' => '',
        'effects' => array(),
        'text' => array(),
        'format' => 'png',
        'caching' => TRUE,
        'node' => NULL,
        'source_image_file' => NULL,
        'target_uri' => NULL,
        'force_hashed_filename' => FALSE,
        'alt' => '',
        'title' => '',
        'attributes' => array(),
        'image_container_attributes' => array(),
        'href' => '',
      ),
    ),
    // Textimage background image effect - summary.
    'textimage_background_effect_summary' => array(
      'variables' => array(
        'data' => NULL,
      ),
    ),
    // Textimage text image effect - summary.
    'textimage_text_effect_summary' => array(
      'variables' => array(
        'data' => NULL,
      ),
    ),
    // Textimage GIF transparency effect - summary.
    'textimage_gif_transparency_effect_summary' => array(
      'variables' => array(
        'data' => NULL,
      ),
    ),
    // Render a string with color formatting.
    'textimage_colored_string' => array(
      'variables' => array(
        'text' => '',
        'foreground_color' => '#000000',
        'background_color' => '',
        'border' => FALSE,
        'border_color' => '#000000',
      ),
    ),
  );
  return $theme;
}

/**
 * Implements hook_menu().
 */
function textimage_menu() {
  $items = array();
  $items['admin/config/media/textimage'] = array(
    'title' => 'Textimage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'textimage_form_settings',
    ),
    'access arguments' => array(
      'administer image styles',
    ),
    'description' => 'Configure Textimage settings.',
    'weight' => 10,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'textimage.admin.inc',
  );
  $items['admin/config/media/textimage/cleanup'] = array(
    'title' => 'Cleanup Textimage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'textimage_flush_all_form',
    ),
    'access arguments' => array(
      'administer image styles',
    ),
    'description' => 'Cleanup Textimage stored images.',
    'type' => MENU_CALLBACK,
    'file' => 'textimage.admin.inc',
  );

  // Generate image derivatives of publicly available files, via
  // URL request path. For local default filesystem only.
  $stream_wrapper = file_stream_wrapper_get_instance_by_scheme(variable_get('file_default_scheme', 'public'));
  if ($stream_wrapper instanceof DrupalLocalStreamWrapper) {
    $directory_path = $stream_wrapper
      ->getDirectoryPath();
    $items[$directory_path . '/textimage'] = array(
      'title' => 'URL textimage generation',
      'page callback' => 'textimage_url_deliver',
      'access arguments' => array(
        'generate textimage url derivatives',
      ),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implements hook_permission().
 */
function textimage_permission() {
  return array(
    'generate textimage url derivatives' => array(
      'title' => t('Generate Textimage URL derivatives'),
      'description' => t('Allows users to generate Textimage derivatives directly from an URL request.'),
    ),
  );
}

/**
 * Implements hook_file_download().
 *
 * Control the access to files underneath the styles directory.
 */
function textimage_file_download($uri) {
  $path = file_uri_target($uri);

  // Private file access for image style derivatives.
  if (strpos($path, 'textimage') === 0) {
    $args = explode('/', $path);

    // Discard the first part of the path (textimage).
    array_shift($args);

    // Discard the style name.
    array_shift($args);

    // Remove the filename from the path.
    array_shift($args);

    // Then the remaining parts are the path to the image.
    $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args);

    // Check that the file exists and is an image.
    if ($info = image_get_info($uri)) {

      // Check the permissions of the original to grant access to this image.
      $headers = module_invoke_all('file_download', $original_uri);
      if (!in_array(-1, $headers)) {
        return array(
          // Send headers describing the image's size, and MIME-type...
          'Content-Type' => $info['mime_type'],
          'Content-Length' => $info['file_size'],
        );
      }
    }
    return -1;
  }

  // Private file access for the original files. Note that we only
  // check access for non-temporary images, since file.module will
  // grant access for all temporary files.
  $files = file_load_multiple(array(), array(
    'uri' => $uri,
  ));
  if (count($files)) {
    $file = reset($files);
    if ($file->status) {
      return file_file_download($uri, 'image');
    }
  }
}

/**
 * Implements hook_flush_caches().
 */
function textimage_flush_caches() {
  return array(
    'cache_textimage',
  );
}

/**
 * Implements hook_cron().
 */
function textimage_cron() {

  // Remove temporary, uncached, image files.
  if (file_exists(_textimage_get_store_path('uncached'))) {
    if (file_unmanaged_delete_recursive(_textimage_get_store_path('uncached'))) {
      _textimage_diag(t("Textimage temporary image files removed."), WATCHDOG_NOTICE);
    }
    else {
      _textimage_diag(t("Textimage could not remove temporary image files."), WATCHDOG_ERROR, __FUNCTION__);
    }
  }
}

/**
 * Implements hook_schema_alter().
 */
function textimage_schema_alter(&$schema) {

  // Alter the schema adding fields required by Textimage.
  require_once 'textimage.install';
  $table_fields = _textimage_define_table_fields();
  foreach ($table_fields as $table_name => $fields) {
    foreach ($fields as $field_name => $field) {
      $schema[$table_name]['fields'][$field_name] = $field;
    }
  }
}

/**
 * Implements hook_token_info().
 */
function textimage_token_info() {
  $type = array(
    'name' => t('Textimage'),
    'description' => t('Tokens related to Textimage'),
    'needs-data' => 'node',
  );
  $tokens = array(
    'uri' => array(
      'name' => t("URI"),
      'description' => t("The URI(s) of a Textimage generated for a node's field. Use like <strong>[textimage:uri:field{:display}{:sequence}]</strong>, where:<br/><strong>field</strong> is the machine name of the field for which the Textimage is generated (e.g. body or field_xxxx);<br/><strong>display</strong> is an optional indication of the display view mode (e.g. default, full, teaser, etc.); 'default' is used if not specified; <br/><strong>sequence</strong> is an optional indication of the URI to return if Textimage produces more images for the same field (like e.g. in a multi-value Image field); if not specified, a comma-delimited string of all the URIs generated will be returned."),
      'dynamic' => TRUE,
    ),
    'url' => array(
      'name' => t("URL"),
      'description' => t("The URL(s) of a Textimage generated for a node's field. Use like <strong>[textimage:url:field{:display}{:sequence}]</strong>, where:<br/><strong>field</strong> is the machine name of the field for which the Textimage is generated (e.g. body or field_xxxx);<br/><strong>display</strong> is an optional indication of the display view mode (e.g. default, full, teaser, etc.); 'default' is used if not specified; <br/><strong>sequence</strong> is an optional indication of the URL to return if Textimage produces more images for the same field (like e.g. in a multi-value Image field); if not specified, a comma-delimited string of all the URLs generated will be returned."),
      'dynamic' => TRUE,
    ),
  );
  return array(
    'types' => array(
      'textimage' => $type,
    ),
    'tokens' => array(
      'textimage' => $tokens,
    ),
  );
}

/**
 * Implements hook_tokens().
 */
function textimage_tokens($type, $tokens, array $data = array(), array $options = array()) {

  // Jumps out if not processing textimage tokens.
  if ($type != 'textimage') {
    return array();
  }

  // Jumps out with a warning if node is not available.
  if (!isset($data['node'])) {
    _textimage_diag(t('Textimage token(s) @tokens_list could not be resolved as no information about the context was available.', array(
      '@tokens_list' => implode(', ', $tokens),
    )), WATCHDOG_WARNING);
    return array();
  }

  // Process tokens.
  $replacements = TextimageImager::processTokens('url', $tokens, $data['node']);
  $replacements += TextimageImager::processTokens('uri', $tokens, $data['node']);
  return $replacements;
}

/**
 * Implements hook_metatag_token_types_alter().
 *
 * Integration of Textimage tokens with the Metatag module.
 */
function textimage_metatag_token_types_alter(&$options) {

  // Add Textimage tokens to the token types managed by Metatag,
  // for the node context.
  if ($options['context'] == 'node') {
    if (!isset($options['token types'])) {
      $options['token types'] = array();
    }
    if (!in_array('textimage', $options['token types'])) {
      $options['token types'][] = 'textimage';
    }
  }
}

// There is not a way to specify a file in hook_image_effect_info.
// Effects .inc files are included here for the time being.
require_once 'effects/textimage_background.inc';
require_once 'effects/textimage_text.inc';
require_once 'effects/textimage_gif_transparency.inc';

/**
 * Implements hook_image_styles_alter().
 */
function textimage_image_styles_alter(&$styles) {

  // Adds textimage properties to the image styles when loading them.
  foreach ($styles as &$style) {
    TextimageStyles::isTextimage($style);
  }
}

/**
 * Implements hook_image_effect_info().
 */
function textimage_image_effect_info() {
  $effects = array();
  $effects['textimage_background'] = array(
    'label' => t('Textimage background'),
    'help' => t('Define size and background color of the Textimage, or a background image.'),
    'effect callback' => 'textimage_background_effect',
    'dimensions callback' => 'textimage_background_effect_dimensions',
    'form callback' => 'textimage_background_effect_form',
    'summary theme' => 'textimage_background_effect_summary',
  );
  $effects['textimage_text'] = array(
    'label' => t('Textimage text'),
    'help' => t('Define text font, size and positioning.'),
    'effect callback' => 'textimage_text_effect',
    'dimensions callback' => 'textimage_text_effect_dimensions',
    'form callback' => 'textimage_text_effect_form',
    'summary theme' => 'textimage_text_effect_summary',
  );
  $effects['textimage_gif_transparency'] = array(
    'label' => t('Textimage GIF transparency'),
    'help' => t('Define a color to set GIF transparency.'),
    'effect callback' => 'textimage_gif_transparency_effect',
    'dimensions passthrough' => TRUE,
    'form callback' => 'textimage_gif_transparency_effect_form',
    'summary theme' => 'textimage_gif_transparency_effect_summary',
  );
  return $effects;
}

/**
 * Implements hook_image_style_flush().
 */
function textimage_image_style_flush($style) {

  // Manage the textimage part of style flushing.
  if (TextimageStyles::isTextimage($style)) {
    TextimageStyles::flush($style);
  }
}

/**
 * Implements hook_image_style_save().
 */
function textimage_image_style_save($style) {
  if (TextimageStyles::isTextimage($style)) {
    TextimageStyles::save($style);
  }
}

/**
 * Implements hook_image_style_delete().
 */
function textimage_image_style_delete($style) {
  if (TextimageStyles::isTextimage($style)) {
    TextimageStyles::delete($style);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * @see _textimage_image_style_form_form_alter()
 */
function textimage_form_image_style_form_alter(&$form, &$form_state, $form_id) {
  require_once 'textimage.admin.inc';
  _textimage_image_style_form_form_alter($form, $form_state, $form_id);
}

/**
 * Implements hook_field_formatter_info().
 */
function textimage_field_formatter_info() {
  $formatters = array(
    'textimage' => array(
      'label' => t('Textimage'),
      'description' => t('Textimage formatter'),
      'field types' => array(
        'image',
        'text',
        'text_with_summary',
        'text_long',
      ),
      'settings' => array(
        'image_style' => '',
        'image_text_values' => 'merge',
        'image_link' => '',
        'image_alt' => '',
        'image_title' => '',
      ),
    ),
  );
  return $formatters;
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function textimage_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  // Image style setting.
  $image_styles = TextimageStyles::getOptions(FALSE);
  $element['image_style'] = array(
    '#title' => t('Image style'),
    '#type' => 'select',
    '#default_value' => $settings['image_style'],
    '#options' => $image_styles,
    '#required' => TRUE,
    '#description' => t('Only Textimage relevant image styles can be selected.'),
  );

  // Multi-value text field image generation settings.
  if ($field['module'] == 'text' && $field['cardinality'] != 1) {
    $options = array(
      'merge' => t("Build one single image, styling together text values."),
      'itemize' => t("Build multiple images, styling each text value in a separate image."),
    );
    $element['image_text_values'] = array(
      '#title' => t('Multiple values text field'),
      '#type' => 'radios',
      '#default_value' => $settings['image_text_values'],
      '#options' => $options,
      '#required' => TRUE,
      '#description' => t("Text values are styled following the sequence of 'Textimage text' effects in the image style."),
    );
  }

  // Link setting.
  $link_types = array(
    'content' => t('Content'),
    'file' => t('File'),
  );
  $element['image_link'] = array(
    '#title' => t('Link image to'),
    '#type' => 'select',
    '#default_value' => $settings['image_link'],
    '#empty_option' => t('Nothing'),
    '#options' => $link_types,
  );

  // Image alt and title attribute settings.
  $element['image_alt'] = array(
    '#title' => t('Alternate text'),
    '#type' => 'textfield',
    '#default_value' => $settings['image_alt'],
    '#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.') . ' ' . t('Tokens can be used.'),
    '#maxlength' => 512,
  );
  $element['image_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#default_value' => $settings['image_title'],
    '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.') . ' ' . t('Tokens can be used.'),
    '#maxlength' => 1024,
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function textimage_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = array();
  $image_styles = TextimageStyles::getOptions(FALSE);

  // Unset possible 'No defined styles' option.
  unset($image_styles['']);

  // Styles could be lost because of enabled/disabled modules that defines
  // their styles in code.
  if (isset($image_styles[$settings['image_style']])) {
    $summary[] = t('Image style: @style', array(
      '@style' => $image_styles[$settings['image_style']],
    ));
  }
  else {
    $summary[] = t('Image style: undefined');
  }

  // Multi-value text field image generation settings.
  if ($field['module'] == 'text' && $field['cardinality'] != 1) {
    $options = array(
      'merge' => t("Build one image"),
      'itemize' => t("Build multiple images"),
    );
    $summary[] = t('Multiple text values:') . ' ' . $options[$settings['image_text_values']];
  }

  // Display link setting only if image is linked.
  $link_types = array(
    'content' => t('Linked to content'),
    'file' => t('Linked to file'),
  );
  if (isset($link_types[$settings['image_link']])) {
    $summary[] = $link_types[$settings['image_link']];
  }

  // Display this setting only if alt text is specified.
  if (!empty($settings['image_alt'])) {
    $summary[] = t('Alternate text: @image_alt', array(
      '@image_alt' => $settings['image_alt'],
    ));
  }

  // Display this setting only if title is specified.
  if (!empty($settings['image_title'])) {
    $summary[] = t('Title: @image_title', array(
      '@image_title' => $settings['image_title'],
    ));
  }
  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_view().
 */
function textimage_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  // If formatting a node, store entity for passing to theme.
  // The node entity will be used for the detokening of text.
  $node = $entity_type == 'node' ? $entity : NULL;

  // Check if the formatter involves a link.
  $href = NULL;
  if ($image_link_setting = $display['settings']['image_link']) {
    switch ($image_link_setting) {
      case 'content':
        $uri = entity_uri($entity_type, $entity);
        $href = $uri['path'];
        break;
      case 'file':
        $href = '#textimage_derivative_url#';
        break;
    }
  }
  $element = array();
  if ($field['module'] == 'text') {

    // Get sanitized text strings from a text field.
    $text = TextimageImager::getTextFieldText($items, $field, $instance, $node);
    if ($field['cardinality'] != 1 && $display['settings']['image_text_values'] == 'itemize') {

      // Build separate image for each text value.
      foreach ($text as $text_value) {
        $element[] = array(
          '#theme' => 'textimage_formatter',
          '#style_name' => $display['settings']['image_style'],
          '#text' => $text_value,
          '#node' => $node,
          '#alt' => $display['settings']['image_alt'],
          '#title' => $display['settings']['image_title'],
          '#href' => $href,
        );
      }
    }
    else {

      // Build single image with all text values.
      $element[] = array(
        '#theme' => 'textimage_formatter',
        '#style_name' => $display['settings']['image_style'],
        '#text' => $text,
        '#node' => $node,
        '#alt' => $display['settings']['image_alt'],
        '#title' => $display['settings']['image_title'],
        '#href' => $href,
      );
    }
  }
  elseif ($field['module'] == 'image') {

    // Get source image from an image field.
    foreach ($items as $delta => $item) {
      $source_image_file = file_load($item['fid']);
      $element[$delta] = array(
        '#theme' => 'textimage_formatter',
        '#style_name' => $display['settings']['image_style'],
        '#text' => NULL,
        '#node' => $node,
        '#source_image_file' => $source_image_file,
        '#force_hashed_filename' => TRUE,
        '#alt' => $display['settings']['image_alt'],
        '#title' => $display['settings']['image_title'],
        '#href' => $href,
      );
    }
  }
  return $element;
}

/**
 * Implements hook_element_info().
 *
 * Implement a form element to enable capturing color information.
 */
function textimage_element_info() {

  // This are the definition for the form API elements.
  return array(
    'textimage_color' => array(
      '#input' => TRUE,
      '#process' => array(
        'textimage_color_element_process',
      ),
      '#element_validate' => array(
        'textimage_color_element_validate',
      ),
    ),
  );
}

/**
 * Process callback for 'textimage_color' form element.
 *
 * Enable capturing color information. Depending on the options, uses a normal
 * textfield or a jquery_colorpicker element to capture the HEX value of a
 * color.
 * The following elements further qualify the process:
 *  '#allow_transparent' - if set to TRUE, a checkbox is displayed to set the
 *    color as a full transparency, In this case, color hex and opacity are
 *    hidden, and the value returned is NULL.
 *  '#allow_opacity' - if set to TRUE, a textfield is displayed to capture the
 *    'opacity' value, as a percentage.
 */
function textimage_color_element_process($element, &$form_state) {

  // Make sure element properties are set.
  $element['#allow_transparent'] = isset($element['#allow_transparent']) ? $element['#allow_transparent'] : FALSE;
  $element['#allow_opacity'] = isset($element['#allow_opacity']) ? $element['#allow_opacity'] : FALSE;
  $element['#description'] = isset($element['#description']) ? $element['#description'] : NULL;

  // In case default value is transparent, set hex and opacity to default
  // values (white, fully opaque) so that if transparency is unchecked,
  // we have a starting value.
  $transparent = empty($element['#default_value']) ? TRUE : FALSE;
  $hex = $transparent ? '#FFFFFF' : drupal_substr($element['#default_value'], 0, 7);
  $opacity = $transparent ? 100 : _textimage_rgba_to_opacity($element['#default_value']);
  if ($element['#allow_transparent'] || $element['#allow_opacity']) {

    // If more sub-fields are needed to define the color, wrap them in a
    // fieldset.
    $element['container'] = array(
      '#type' => 'fieldset',
      '#description' => $element['#description'],
      '#title' => $element['#title'],
    );

    // Checkbox for transparency.
    if ($element['#allow_transparent']) {
      $element['container']['transparent'] = array(
        '#type' => 'checkbox',
        '#title' => t('Transparent'),
        '#default_value' => $transparent,
      );
    }

    // Color field.
    // Renders a jquery_colorpicker element if available and enabled, otherwise
    // a textfield.
    if (_textimage_get_variable('color_selector') == 'jquery_colorpicker') {
      $element['container']['hex'] = array(
        '#title' => t('Color'),
        '#type' => 'jquery_colorpicker',
        '#default_value' => drupal_substr($hex, -6),
      );
    }
    else {
      $element['container']['hex'] = array(
        '#title' => t('Color'),
        '#type' => 'textfield',
        '#default_value' => $hex,
        '#maxlength' => 7,
        '#size' => 7,
        '#element_validate' => array(
          'image_effect_color_validate',
        ),
      );
    }

    // States management for color field.
    $element['container']['hex']['#states'] = array(
      'visible' => array(
        ':input[name="' . $element['#name'] . '[container][transparent]"]' => array(
          'checked' => FALSE,
        ),
      ),
    );

    // Textfield for opacity.
    if ($element['#allow_opacity']) {
      $element['container']['opacity'] = array(
        '#type' => 'textfield',
        '#title' => t('Opacity'),
        '#default_value' => $opacity,
        '#maxlength' => 3,
        '#size' => 2,
        '#field_suffix' => '%',
        '#element_validate' => array(
          'element_validate_integer',
        ),
        '#states' => array(
          'visible' => array(
            ':input[name="' . $element['#name'] . '[container][transparent]"]' => array(
              'checked' => FALSE,
            ),
          ),
        ),
      );
    }
  }
  else {

    // No transparency or opacity, straight color field.
    if (_textimage_get_variable('color_selector') == 'jquery_colorpicker') {
      $element['hex'] = array(
        '#type' => 'jquery_colorpicker',
        '#title' => $element['#title'],
        '#description' => $element['#description'],
        '#default_value' => drupal_substr($hex, -6),
      );
    }
    else {
      $element['hex'] = array(
        '#type' => 'textfield',
        '#title' => $element['#title'],
        '#description' => $element['#description'],
        '#default_value' => $hex,
        '#maxlength' => 7,
        '#size' => 7,
        '#element_validate' => array(
          'image_effect_color_validate',
        ),
      );
    }
  }
  unset($element['#description'], $element['#title']);
  return $element;
}

/**
 * Validation callback for 'textimage_color' form element.
 */
function textimage_color_element_validate(&$element, &$form_state, $form) {

  // Normalize returned element values to a rgba hex value.
  if ($element['#allow_transparent'] && $element['container']['transparent']['#value']) {
    $element['#value'] = NULL;
  }
  elseif ($element['#allow_transparent'] || $element['#allow_opacity']) {
    $element['#value'] = drupal_strtoupper($element['container']['hex']['#value']);
  }
  else {
    $element['#value'] = drupal_strtoupper($element['hex']['#value']);
  }
  if ($element['#value'] && $element['#value'][0] != '#') {
    $element['#value'] = '#' . $element['#value'];
  }
  if ($element['#value'] && $element['#allow_opacity']) {
    $element['#value'] .= _textimage_opacity_to_alpha($element['container']['opacity']['#value']);
  }

  // Replace the element value in the parent form with the normalized value.
  $value =& $form_state['values'];
  foreach ($element['#parents'] as $path) {
    $value =& $value[$path];
  }
  $value = $element['#value'];
}

/**
 * Textimage functions.
 */

/**
 * Theme function to get a Textimage from a stored image style.
 */
function theme_textimage_style_image($variables) {
  return theme('textimage_formatter', array(
    'style_name' => $variables['style_name'],
    'text' => $variables['text'],
    'format' => $variables['format'],
    'caching' => $variables['caching'],
    'node' => $variables['node'],
    'source_image_file' => $variables['source_image_file'],
    'alt' => $variables['alt'] ? $variables['alt'] : implode(' - ', $variables['text']),
    'title' => $variables['title'],
    'attributes' => $variables['attributes'],
  ));
}

/**
 * Theme function to get a Textimage from a programmatical image style.
 */
function theme_textimage_direct_image($variables) {
  return theme('textimage_formatter', array(
    'effects' => $variables['effects'],
    'text' => $variables['text'],
    'format' => $variables['format'],
    'caching' => $variables['caching'],
    'alt' => $variables['alt'] ? $variables['alt'] : implode(' - ', $variables['text']),
    'title' => $variables['title'],
    'attributes' => $variables['attributes'],
  ));
}

/**
 * Theme function to format a Textimage.
 */
function theme_textimage_formatter($variables) {
  $output = NULL;

  // Get URI to image file.
  if ($variables['textimage'] && $variables['textimage'] instanceof Textimage) {
    $image_uri = $variables['textimage']
      ->getUri();
  }
  else {
    $data = array(
      'style_name' => $variables['style_name'],
      'effects' => $variables['effects'],
      'text' => $variables['text'],
      'target_uri' => $variables['target_uri'],
      'extension' => $variables['format'],
      'caching' => $variables['caching'],
      'node' => $variables['node'],
      'source_image_file' => $variables['source_image_file'],
      'force_hashed_filename' => $variables['force_hashed_filename'],
    );
    $image = TextimageImager::getTextimage($data);
    $image_uri = $image
      ->getUri();
  }

  // Render through theme_image.
  if ($image_uri) {
    $image_info = image_get_info($image_uri);

    // Get the <img>.
    $output = theme('image', array(
      'path' => $image_uri,
      'width' => $image_info['width'],
      'height' => $image_info['height'],
      'alt' => TextimageImager::processTextString($variables['alt'], NULL, $variables['node'], $variables['source_image_file']),
      'title' => TextimageImager::processTextString($variables['title'], NULL, $variables['node'], $variables['source_image_file']),
      'attributes' => $variables['attributes'],
    ));
    $image_url = file_create_url($image_uri);

    // Wrap the <img> in a container if requested.
    if (!empty($variables['image_container_attributes'])) {
      $container_attributes = array();
      foreach ($variables['image_container_attributes'] as $key => $value) {
        $container_attributes[$key] = str_replace('#textimage_derivative_url#', $image_url, $value);
      }
      $output = theme('container', array(
        'element' => array(
          '#attributes' => $container_attributes,
          '#children' => $output,
        ),
      ));
    }

    // Use the output to build a link if href specified.
    if ($variables['href']) {
      $options['html'] = TRUE;
      $href = $variables['href'] == '#textimage_derivative_url#' ? $image_url : $variables['href'];
      $output = l($output, $href, $options);
    }
  }
  return $output;
}

/**
 * Deliver directly a Textimage from the URL request.
 */
function textimage_url_deliver() {

  // Get the real directory of the public wrapper.
  $public_path = file_stream_wrapper_get_instance_by_scheme('public')
    ->getDirectoryPath();

  // Full path to public textimage files.
  $public_textimage_path = base_path() . $public_path . '/textimage/';
  $pattern = '/' . str_replace('/', '\\/', $public_textimage_path) . '/';

  // The request in format [style]/[Text_0]/[Text_1]/.../[Text_n].[extension]
  // is in the trailing part of the URL.
  $trail = urldecode(preg_replace($pattern, '', request_uri()));
  $args = explode('/', $trail);

  // Manages the [style].
  $style_name = array_shift($args);
  $style = TextimageStyles::get($style_name);
  if (!$style) {
    drupal_not_found();
  }
  if ($style['textimage']['uri_scheme'] != 'public') {
    drupal_access_denied();
  }

  // [Text_0]/[Text_1]/.../[Text_n] to the $text array.
  $text = $args;

  // Manage the [extension].
  $last_text = array_pop($text);
  $offset = strrpos($last_text, '.');
  if ($offset && strlen($last_text) - $offset <= 5) {
    $extension = substr($last_text, $offset + 1);
    $text[] = substr($last_text, 0, $offset);
  }
  else {
    $extension = 'png';
    $text[] = $last_text;
  }

  // Get the image and transfer to client.
  $uri = TextimageImager::processImageRequest($style, NULL, $text, $extension);
  $image_info = image_get_info($uri);
  file_transfer($uri, array(
    'Content-Type' => $image_info['mime_type'],
    'Content-Length' => $image_info['file_size'],
  ));
}

/**
 * Render a text string, color formatted.
 *
 * Optionally, uses Luma algorithms to determine best color match
 * for foreground and border colors.
 */
function theme_textimage_colored_string($variables) {
  drupal_add_css(drupal_get_path('module', 'textimage') . '/misc/css/textimage.admin.css');

  // Set background color.
  if (!empty($variables['background_color'])) {
    $background_color = drupal_substr($variables['background_color'], 0, 7);
    $background_color_css = "background-color:{$background_color};";
  }
  else {
    $background_color = "#FFFFFF";
    $background_color_css = NULL;
  }

  // Set text.
  $text = $variables['text'];

  // Set text foreground color. If 'matchLuma' is specified, resolves
  // best color against background.
  if ($variables['foreground_color'] == 'matchLuma') {
    $foreground_color = textimage_match_luma($background_color);
  }
  else {
    $foreground_color = drupal_substr($variables['foreground_color'], 0, 7);
  }

  // Set border css if required.  If 'matchLuma' is specified for border
  // color, resolves best color against background.
  $border_css = NULL;
  if ($variables['border']) {
    if ($variables['border_color'] == 'matchLuma') {
      $border_color = textimage_match_luma($background_color);
    }
    else {
      $border_color = drupal_substr($variables['border_color'], 0, 7);
    }
    $border_css = "border-color:{$border_color};";
  }
  return "<span class=\"textimage-colored-string\" style=\"{$background_color_css} color:{$foreground_color}; {$border_css} \">{$text}</span>";
}

/**
 * Determine best match to over/underlay a defined color.
 *
 * Calculates UCCIR 601 luma of the entered color and returns a black or
 * white color to ensure readibility.
 *
 * @see http://en.wikipedia.org/wiki/Luma_video
 */
function textimage_match_luma($rgba, $soft = FALSE) {
  list($r, $g, $b) = array_values(imagecache_actions_hex2rgba($rgba));
  $luma = 1 - (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
  if ($luma < 0.5) {

    // Bright colors - black.
    $d = 0;
  }
  else {

    // Dark colors - white.
    $d = 255;
  }
  return _color_pack(array(
    $d,
    $d,
    $d,
  ));
}

/**
 * Display/log a diagnostic message.
 *
 * @param string $message
 *   message
 * @param int $severity
 *   severity as per watchdog()
 * @param string $caller
 *   the calling function
 */
function _textimage_diag($message, $severity, $caller = NULL, $user_messages = TRUE) {
  if ($caller) {
    $message = t("@message - Function: @caller", array(
      '@message' => $message,
      '@caller' => $caller,
    ));
  }
  if ($severity <= WATCHDOG_NOTICE) {
    watchdog('textimage', $message, array(), $severity);
  }
  if ($severity <= variable_get('textimage_message_level', WATCHDOG_WARNING) && $user_messages) {
    switch ($severity) {
      case WATCHDOG_DEBUG:
      case WATCHDOG_INFO:
      case WATCHDOG_NOTICE:
        $type = 'status';
        break;
      case WATCHDOG_WARNING:
        $type = 'warning';
        break;
      default:
        $type = 'error';
    }
    drupal_set_message($message, $type, FALSE);
  }
}

/**
 * Return a 'textimage' configuration variable.
 *
 * @param string $variable
 *   textimage settings variable to be returned, or NULL
 *   to return the entire config.
 *
 * @return string|array
 *   value of the requested configuration variable, or the entire array
 *   if no variable specified in input
 */
function _textimage_get_variable($variable = NULL) {
  $vars =& drupal_static(__FUNCTION__);
  if (!isset($vars)) {
    $vars = variable_get('textimage', array());
    $vars = drupal_array_merge_deep(array(
      'store_scheme' => 'private',
      'fonts_handling_module' => 'textimage',
      'fonts_path' => 'private://textimage_store/fonts',
      'default_font' => array(
        'name' => '',
        'uri' => '',
      ),
      'backgrounds_handling_module' => 'textimage',
      'backgrounds_path' => 'private://textimage_store/backgrounds',
      'color_selector' => 'textbox',
    ), $vars);
  }
  if ($variable) {
    return isset($vars[$variable]) ? $vars[$variable] : NULL;
  }
  else {
    return $vars;
  }
}

/**
 * Store a 'textimage' configuration variable.
 *
 * @param string|array $variable
 *   the name of the textimage configuration variable, or the entire
 *   configuration array
 * @param string $value
 *   the value of the textimage configuration variable to store
 */
function _textimage_set_variable($variable, $value = NULL) {
  if (is_array($variable)) {
    $vars = $variable;
  }
  else {
    $vars = _textimage_get_variable();
    if ($variable) {
      $vars[$variable] = $value;
    }
  }
  $vars = array_intersect_key($vars, array(
    'store_scheme' => NULL,
    'fonts_handling_module' => NULL,
    'fonts_path' => NULL,
    'default_font' => array(),
    'backgrounds_handling_module' => NULL,
    'backgrounds_path' => NULL,
    'color_selector' => NULL,
  ));
  variable_set('textimage', $vars);
  drupal_static_reset('_textimage_get_variable');
}

/**
 * Helper - check if a module is enabled and at a specified release level.
 *
 * @param string $module
 *   Module name.
 * @param string $version
 *   (optional) Module version.
 * @param string $comparison_operator
 *   (optional) A comparison operator to test against $version.
 *
 * @return bool
 *   TRUE if module is enabled, FALSE otherwise.
 */
function _textimage_module_exists($module, $version = NULL, $comparison_operator = '>=') {
  if (!module_exists($module)) {
    return FALSE;
  }
  if ($version) {
    $module_info = system_get_info('module', $module);
    if (empty($module_info['version'])) {
      return TRUE;
    }
    return version_compare($module_info['version'], $version, $comparison_operator);
  }
  return TRUE;
}

/**
 * Convert RGBA alpha to percent opacity.
 *
 * @param string $rgba
 *   RGBA hex.
 *
 * @return int
 *   Opacity as percentage (0 = transparent, 100 = fully opaque).
 */
function _textimage_rgba_to_opacity($rgba) {
  $hex = drupal_substr($rgba, 7, 2);
  return $hex ? floor(-(hexdec($hex) - 127) / 127 * 100) : 100;
}

/**
 * Convert percent opacity to hex alpha.
 *
 * @param int $value
 *   Opacity as percentage (0 = transparent, 100 = fully opaque).
 *
 * @return string
 *   Opacity as HEX.
 */
function _textimage_opacity_to_alpha($value) {
  return $value == NULL ? NULL : drupal_strtoupper(str_pad(dechex(-($value - 100) / 100 * 127), 2, "0", STR_PAD_LEFT));
}

/**
 * Cleanup Textimage.
 *
 * This will remove all image files generated via Textimage, flush all
 * the image styles, clear all cache and all store entries on the db.
 */
function _textimage_flush_all() {
  foreach (image_styles() as $style) {
    if (TextimageStyles::isTextimage($style)) {
      image_style_flush($style);
    }
  }
  if (file_exists(_textimage_get_store_path('unstyled_hashed'))) {
    file_unmanaged_delete_recursive(_textimage_get_store_path('unstyled_hashed'));
  }
  if (file_exists(_textimage_get_store_path('uncached'))) {
    file_unmanaged_delete_recursive(_textimage_get_store_path('uncached'));
  }
  cache_clear_all(NULL, 'cache_textimage');
  db_truncate('textimage_store')
    ->execute();
  _textimage_diag(t('All Textimage images were removed.'), WATCHDOG_NOTICE);
}

/**
 * Return a path within the textimage_store structure.
 */
function _textimage_get_store_path($path) {
  return _textimage_get_variable('store_scheme') . '://textimage_store/' . $path;
}

/**
 * Load Textimage toolkit dependent functions.
 */
function _textimage_load_toolkit_functions() {

  // Include toolkit specific image functions.
  $toolkit_functions_filename = dirname(__FILE__) . '/effects/textimage_text.' . image_get_toolkit() . '.inc';
  if (is_file($toolkit_functions_filename)) {
    require_once $toolkit_functions_filename;
    return TRUE;
  }
  else {
    $toolkits = image_get_available_toolkits();
    _textimage_diag(t("Textimage does not support the '@toolkit' image toolkit.", array(
      '@toolkit' => $toolkits[image_get_toolkit()],
    )), WATCHDOG_ERROR);
    return FALSE;
  }
}

Functions

Namesort descending Description
textimage_color_element_process Process callback for 'textimage_color' form element.
textimage_color_element_validate Validation callback for 'textimage_color' form element.
textimage_cron Implements hook_cron().
textimage_element_info Implements hook_element_info().
textimage_field_formatter_info Implements hook_field_formatter_info().
textimage_field_formatter_settings_form Implements hook_field_formatter_settings_form().
textimage_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
textimage_field_formatter_view Implements hook_field_formatter_view().
textimage_file_download Implements hook_file_download().
textimage_flush_caches Implements hook_flush_caches().
textimage_form_image_style_form_alter Implements hook_form_FORM_ID_alter().
textimage_help Implements hook_help().
textimage_image_effect_info Implements hook_image_effect_info().
textimage_image_styles_alter Implements hook_image_styles_alter().
textimage_image_style_delete Implements hook_image_style_delete().
textimage_image_style_flush Implements hook_image_style_flush().
textimage_image_style_save Implements hook_image_style_save().
textimage_match_luma Determine best match to over/underlay a defined color.
textimage_menu Implements hook_menu().
textimage_metatag_token_types_alter Implements hook_metatag_token_types_alter().
textimage_permission Implements hook_permission().
textimage_schema_alter Implements hook_schema_alter().
textimage_theme Implements hook_theme().
textimage_tokens Implements hook_tokens().
textimage_token_info Implements hook_token_info().
textimage_url_deliver Deliver directly a Textimage from the URL request.
theme_textimage_colored_string Render a text string, color formatted.
theme_textimage_direct_image Theme function to get a Textimage from a programmatical image style.
theme_textimage_formatter Theme function to format a Textimage.
theme_textimage_style_image Theme function to get a Textimage from a stored image style.
_textimage_diag Display/log a diagnostic message.
_textimage_flush_all Cleanup Textimage.
_textimage_get_store_path Return a path within the textimage_store structure.
_textimage_get_variable Return a 'textimage' configuration variable.
_textimage_load_toolkit_functions Load Textimage toolkit dependent functions.
_textimage_module_exists Helper - check if a module is enabled and at a specified release level.
_textimage_opacity_to_alpha Convert percent opacity to hex alpha.
_textimage_rgba_to_opacity Convert RGBA alpha to percent opacity.
_textimage_set_variable Store a 'textimage' configuration variable.

Constants