| 1 |
<?php
|
| 2 |
// $ID: $
|
| 3 |
/**
|
| 4 |
* Helper functions for imagecache_textactions
|
| 5 |
*
|
| 6 |
* Static text2canvas and dynamic caption functions
|
| 7 |
*
|
| 8 |
* Ported by dman
|
| 9 |
* from http://drupal.org/node/264862#comment-865490 by patrickharris
|
| 10 |
*
|
| 11 |
* TODO Needs review and clean up, this is a re-merge of branched code, may
|
| 12 |
* contain duplicate stuff that should be combined.
|
| 13 |
*
|
| 14 |
* Currently - The way the two methods calculate the bounding box/offset are
|
| 15 |
* totally different.
|
| 16 |
*
|
| 17 |
* The Settings form is largely duplicated
|
| 18 |
*
|
| 19 |
* The bottom-20 positioning that dynamic text uses is good, and should be
|
| 20 |
* worked back into other dimension calculations.
|
| 21 |
*
|
| 22 |
* Contains a stub for imageapi functions : imageapi_image_overlaytext_alpha
|
| 23 |
* that may be ported over to there if that makes sense.
|
| 24 |
*/
|
| 25 |
|
| 26 |
/**
|
| 27 |
* Place text on top of the current canvas
|
| 28 |
*
|
| 29 |
* Implementation of imagecache_hook_form()
|
| 30 |
*
|
| 31 |
* @param $action array of settings for this action
|
| 32 |
* @return a form definition
|
| 33 |
*/
|
| 34 |
function textactions_text2canvas_form($action) {
|
| 35 |
$defaults = array(
|
| 36 |
'size' => 12,
|
| 37 |
'angle' => 0,
|
| 38 |
'xpos' => left,
|
| 39 |
'ypos' => bottom,
|
| 40 |
'RGB' => array(
|
| 41 |
'R' => 33,
|
| 42 |
'G' => 33,
|
| 43 |
'B' => 33,
|
| 44 |
),
|
| 45 |
'fontfile' => 'MgOpenModernaBold.ttf',
|
| 46 |
'text' => 'Hello World!',
|
| 47 |
);
|
| 48 |
$action = array_merge($defaults, (array)$action);
|
| 49 |
$form = array(
|
| 50 |
'size' => array(
|
| 51 |
'#type' => 'textfield',
|
| 52 |
'#title' => t('Size'),
|
| 53 |
'#default_value' => $action['size'],
|
| 54 |
'#description' => t('Size: The font size. Depending on your version of GD, this should be specified as the pixel size (GD1) or point size (GD2).'),
|
| 55 |
'#size' => 3,
|
| 56 |
),
|
| 57 |
'angle' => array(
|
| 58 |
'#type' => 'textfield',
|
| 59 |
'#title' => t('Angle'),
|
| 60 |
'#default_value' => $action['angle'],
|
| 61 |
'#description' => t('Angle: The angle in degrees, with 0 degrees being left-to-right reading text. Higher values represent a counter-clockwise rotation. For example, a value of 90 would result in bottom-to-top reading text.'),
|
| 62 |
'#size' => 3,
|
| 63 |
),
|
| 64 |
'alpha' => array(
|
| 65 |
'#type' => 'textfield',
|
| 66 |
'#title' => t('Opacity'),
|
| 67 |
'#default_value' => $action['alpha'] ? $action['alpha'] : 100,
|
| 68 |
'#size' => 3,
|
| 69 |
'#description' => t('Opacity: 1-100.'),
|
| 70 |
),
|
| 71 |
'xpos' => array(
|
| 72 |
'#type' => 'textfield',
|
| 73 |
'#title' => t('X offset'),
|
| 74 |
'#default_value' => $action['xpos'],
|
| 75 |
'#description' => t('Enter an offset in pixels or use a keyword: <em>left</em>, <em>center</em>, or <em>right</em>. Syntax like <em>right-20</em> is also valid.'),
|
| 76 |
'#size' => 10,
|
| 77 |
),
|
| 78 |
'ypos' => array(
|
| 79 |
'#type' => 'textfield',
|
| 80 |
'#title' => t('Y offset'),
|
| 81 |
'#default_value' => $action['ypos'],
|
| 82 |
'#description' => t('Enter an offset in pixels or use a keyword: <em>top</em>, <em>center</em>, or <em>bottom</em>. Syntax like <em>bottom-20</em> is also valid.'),
|
| 83 |
'#size' => 10,
|
| 84 |
),
|
| 85 |
'RGB' => imagecache_rgb_form($action['RGB']),
|
| 86 |
'fontfile' => array(
|
| 87 |
'#type' => 'textfield',
|
| 88 |
'#title' => t('Font file name'),
|
| 89 |
'#default_value' => $action['fontfile'],
|
| 90 |
'#description' => t('Font file is either the full system path (eg <code>/usr/share/fonts/truetype/ttf-bitstream-vera/VeraMono.ttf</code>), a font file inside the in the module dir "%moduledir" or the files "%filedir" folder). Example: "arial.ttf". You <em>might</em> find a list of fonts available at !helplink if your system supports it.', array('%moduledir' => drupal_get_path('module', 'imagecache_textactions'), '%filedir' => file_directory_path(), '!helplink' => l('fonts help', 'admin/help/imagecache_textactions') )),
|
| 91 |
),
|
| 92 |
'text' => array(
|
| 93 |
'#type' => 'textarea',
|
| 94 |
'#rows' => 7,
|
| 95 |
'#title' => t('Text'),
|
| 96 |
'#default_value' => $action['text'],
|
| 97 |
'#description' => t('The text string.'),
|
| 98 |
),
|
| 99 |
'evaluate_text' => array(
|
| 100 |
'#type' => 'checkbox',
|
| 101 |
'#title' => t('Evaluate text as PHP code'),
|
| 102 |
'#default_value' => $action['evaluate_text'],
|
| 103 |
'#description' => t('If selected, the text will be treated as PHP code.
|
| 104 |
<p>Enter PHP code that will <b>return</b> your dynamic text. Do not use %php tags.
|
| 105 |
<br />EG <code>return format_date(time());</code>
|
| 106 |
</p><p>Note that executing incorrect PHP-code can break your Drupal site.
|
| 107 |
</p><p>You can access the $caption array which contains:<br />
|
| 108 |
<b>$caption[\'path\']</b> Name of file, e.g. image.jpg<br />
|
| 109 |
If the image is a cck imagefield, you will also have access to:<br />
|
| 110 |
<b>$caption[\'title\']</b> optional imagefield \'title\' text<br />
|
| 111 |
<b>$caption[\'alt\']</b> optional imagefield \'alt\' text<br />
|
| 112 |
<b>$caption[\'node\']</b> the complete node variable that the image is attached to.</p>',
|
| 113 |
array('%php' => '<?php ?>'))
|
| 114 |
),
|
| 115 |
|
| 116 |
);
|
| 117 |
$form['#validate'][] = 'textactions_text2canvas_validate';
|
| 118 |
return $form;
|
| 119 |
}
|
| 120 |
|
| 121 |
/**
|
| 122 |
* TODO Validation will not trigger in D6 unless imagecache.module
|
| 123 |
* applies a patch first.
|
| 124 |
*/
|
| 125 |
function textactions_text2canvas_validate($form) {
|
| 126 |
if (! $fontfile = textactions_find_font($form['data']['fontfile']['#value'], TRUE)) {
|
| 127 |
// Just warn, don't prevent
|
| 128 |
drupal_set_message(t("Unable to confirm that the font %fontfile is available on your system. This may fail, or your system may provide an appropriate fallback, depending on your toolkit behaviour.", array('%fontfile' => $form['data']['fontfile']['#value'])), 'warning' );
|
| 129 |
}
|
| 130 |
else {
|
| 131 |
drupal_set_message(t("Font was found at %fontfile", array('%fontfile' => $fontfile)));
|
| 132 |
}
|
| 133 |
if (!is_numeric($form['data']['alpha']['#value']) || $form['data']['alpha']['#value']<1 || $form['data']['alpha']['#value']>100) {
|
| 134 |
form_set_error('alpha', t('Opacity must be a number between 1 and 100.'));
|
| 135 |
}
|
| 136 |
}
|
| 137 |
|
| 138 |
/**
|
| 139 |
* Implementation of theme_hook() for imagecache_ui.module
|
| 140 |
*/
|
| 141 |
function theme_textactions_text2canvas($element) {
|
| 142 |
$data = $element['#value'];
|
| 143 |
return "<em><strong>". $data['text'] ."</strong></em>" ;
|
| 144 |
}
|
| 145 |
|
| 146 |
/**
|
| 147 |
* Place the source image on the current background
|
| 148 |
*
|
| 149 |
* Implementation of hook_image()
|
| 150 |
*
|
| 151 |
* @param $image
|
| 152 |
* @param $action
|
| 153 |
*/
|
| 154 |
function textactions_text2canvas_image(&$image, $action = array()) {
|
| 155 |
$fontpath = textactions_find_font($action['fontfile']);
|
| 156 |
if (! $fontpath) {
|
| 157 |
drupal_set_message(t("Failed to locate the requested font %fontfile. Cannot overlay text onto image.", array('%fontfile' => $action['fontfile'])));
|
| 158 |
return FALSE;
|
| 159 |
}
|
| 160 |
|
| 161 |
if ($action['evaluate_text']) {
|
| 162 |
$text = textactions_evaluate_text($image, $action);
|
| 163 |
}
|
| 164 |
else {
|
| 165 |
$text = $action['text'];
|
| 166 |
}
|
| 167 |
|
| 168 |
// Calculate position by first creating a temp image containing the text and accessing its dimensions
|
| 169 |
$temp = textactions_create_font_image($action['size'], $action['angle'], $fontpath, $text );
|
| 170 |
|
| 171 |
if(! $temp) {
|
| 172 |
drupal_set_message(t('Failed to generate text image. Cannot calculate text dimensions. Not overlaying text.'), 'error');
|
| 173 |
return;
|
| 174 |
}
|
| 175 |
|
| 176 |
// parse keywords
|
| 177 |
$x_ins = textactions_keyword_filter($action['xpos'], $image->info['width'], $temp['width'], $temp['bx'], 'x');
|
| 178 |
$y_ins = textactions_keyword_filter($action['ypos'], $image->info['height'], $temp['height'], $temp['by'], 'y');
|
| 179 |
|
| 180 |
// convert color from hex (as it is stored in the UI)
|
| 181 |
if($action['RGB']['HEX'] && $deduced = hex_to_rgb($action['RGB']['HEX'])) {
|
| 182 |
$action['RGB'] = array_merge($action['RGB'], $deduced);
|
| 183 |
}
|
| 184 |
|
| 185 |
$action['alpha'] = ($action['alpha'] / 100); //Convert to decimal between 0 and 1
|
| 186 |
$action['RGB']['alpha'] = ((1 - $action['alpha']) * 127); //convert opacity to proper alpha value (0 = opaque, 127 = transparent)
|
| 187 |
|
| 188 |
return imageapi_image_overlaytext_alpha($image, $text, $action['size'], $x_ins, $y_ins, $action['RGB'], $fontpath, $action['angle']);
|
| 189 |
}
|
| 190 |
|
| 191 |
/**
|
| 192 |
* Generate the dynamic text for this image.
|
| 193 |
* Was textactions caption - now merged as an option of text2canvas
|
| 194 |
*
|
| 195 |
* TODO further code review for safety etc
|
| 196 |
*
|
| 197 |
* @param $image object, such as it is
|
| 198 |
* @param $action definition
|
| 199 |
*
|
| 200 |
* @return $text Plain string to be placed on the imagecache process.
|
| 201 |
*/
|
| 202 |
function textactions_evaluate_text($image, $action) {
|
| 203 |
// store our variables here
|
| 204 |
$caption=array();
|
| 205 |
|
| 206 |
// Find the image name
|
| 207 |
$caption['path'] = substr($image->source, strrpos($image->source, '/') + 1);
|
| 208 |
|
| 209 |
// If the image is a cck imagefield,
|
| 210 |
// Find the image's optional title and alt text, as well as its node
|
| 211 |
if (module_exists('imagefield')) {
|
| 212 |
// a lot of work to find if the content_field_image_cache actually exists yet
|
| 213 |
$table_found = false;
|
| 214 |
$r = mysql_query("SHOW TABLES");
|
| 215 |
while($row = mysql_fetch_array($r)) {
|
| 216 |
if($row[0] == 'content_field_image_cache') {
|
| 217 |
$table_found = true;
|
| 218 |
break;
|
| 219 |
}
|
| 220 |
}
|
| 221 |
if($table_found) {
|
| 222 |
// if content_field_image_cache exists,
|
| 223 |
// see if we can find info on the current image
|
| 224 |
$result = db_fetch_object(db_query("SELECT c.nid, c.field_image_cache_title, c.field_image_cache_alt FROM {content_field_image_cache} c INNER JOIN {files} f ON c.field_image_cache_fid=f.fid WHERE f.filepath = '%s'", $caption['path']));
|
| 225 |
}
|
| 226 |
if ($result) {
|
| 227 |
$caption['title']=$result->field_image_cache_title;
|
| 228 |
$caption['alt']=$result->field_image_cache_alt;
|
| 229 |
$caption['node']=node_load($result->nid);
|
| 230 |
}
|
| 231 |
}
|
| 232 |
// TODO more info, like supporting data for image.module image info
|
| 233 |
|
| 234 |
// Process the php using drupal_eval (rather than eval), but with GLOBAL variables, so they can be passed successfully
|
| 235 |
$GLOBALS['caption'] = $caption;
|
| 236 |
|
| 237 |
$text = drupal_eval('<'.'?php global $caption; '.$action['text'].' ?'.'>');
|
| 238 |
$text = eval($action['text']);
|
| 239 |
$text = check_plain($text);
|
| 240 |
|
| 241 |
return $text;
|
| 242 |
}
|
| 243 |
|
| 244 |
|
| 245 |
/**
|
| 246 |
* Creates an image containing only the text - used to calculate the true
|
| 247 |
* bounding box.
|
| 248 |
*/
|
| 249 |
function textactions_create_font_image( $size, $angle, $font, $char ) {
|
| 250 |
$rect = imagettfbbox( $size, 0, $font, $char );
|
| 251 |
if(!$rect) {
|
| 252 |
return NULL;
|
| 253 |
}
|
| 254 |
if( 0 == $angle ) {
|
| 255 |
$imh = $rect[1] - $rect[7];
|
| 256 |
$imw = $rect[2] - $rect[0];
|
| 257 |
$bx = -1 - $rect[0];
|
| 258 |
$by = -1 - $rect[7];
|
| 259 |
} else {
|
| 260 |
$rad = deg2rad( $angle );
|
| 261 |
$sin = sin( $rad );
|
| 262 |
$cos = cos( $rad );
|
| 263 |
if( $angle > 0 ) {
|
| 264 |
$tmp = $rect[6] * $cos + $rect[7] * $sin;
|
| 265 |
$bx = -1 - round( $tmp );
|
| 266 |
$imw = round( $rect[2] * $cos + $rect[3] * $sin - $tmp );
|
| 267 |
$tmp = $rect[5] * $cos - $rect[4] * $sin;
|
| 268 |
$by = -1 - round( $tmp );
|
| 269 |
$imh = round( $rect[1] * $cos - $rect[0] * $sin - $tmp );
|
| 270 |
} else {
|
| 271 |
$tmp = $rect[0] * $cos + $rect[1] * $sin;
|
| 272 |
$bx = -1 - round( $tmp );
|
| 273 |
$imw = round( $rect[4] * $cos + $rect[5] * $sin - $tmp );
|
| 274 |
$tmp = $rect[7] * $cos - $rect[6] * $sin;
|
| 275 |
$by = -1 - round( $tmp );
|
| 276 |
$imh = round( $rect[3] * $cos - $rect[2] * $sin - $tmp );
|
| 277 |
}
|
| 278 |
}
|
| 279 |
|
| 280 |
return array('width'=>$imw, 'height'=>$imh, 'bx'=>$bx, 'by'=>$by);
|
| 281 |
}
|
| 282 |
|
| 283 |
/**
|
| 284 |
* Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
|
| 285 |
*/
|
| 286 |
function textactions_keyword_filter($value, $current_pixels, $new_pixels, $adj, $xy) {
|
| 287 |
// check if we have plus or minus values
|
| 288 |
$v = explode('+', $value);
|
| 289 |
$v2 = explode('-', $value);
|
| 290 |
if ($v2[1]) {$v[1]=-intval($v2[1]); $v[0]=$v2[0];}
|
| 291 |
|
| 292 |
switch ($v[0]) {
|
| 293 |
case 'top':
|
| 294 |
case 'left':
|
| 295 |
$value = 0;
|
| 296 |
break;
|
| 297 |
case 'bottom':
|
| 298 |
case 'right':
|
| 299 |
$value = $current_pixels - $new_pixels;
|
| 300 |
break;
|
| 301 |
case 'center':
|
| 302 |
$value = $current_pixels/2 - $new_pixels/2;
|
| 303 |
break;
|
| 304 |
}
|
| 305 |
|
| 306 |
// perform the adjustment
|
| 307 |
$value=$value+$adj;
|
| 308 |
// add any extra negative or positive
|
| 309 |
if ($v[1]) {$value=$value+$v[1];}
|
| 310 |
#print $current_pixels.' . '.$new_pixels.' . '.$adj.' . '.$xy.'<br />';if ($xy=='y'){exit;}
|
| 311 |
return $value;
|
| 312 |
}
|
| 313 |
|
| 314 |
|
| 315 |
|
| 316 |
/**
|
| 317 |
* Place text on an image.
|
| 318 |
*
|
| 319 |
* @ingroup imageapi
|
| 320 |
*/
|
| 321 |
function imageapi_image_overlaytext_alpha(&$image, $text, $size = 12, $x = 0, $y = 0, $RGB = 0, $fontfile = 'MgOpenModernaBold', $angle = 0) {
|
| 322 |
return call_user_func($image->toolkit .'_image_overlaytext_alpha', $image, $text, $size, $x, $y, $RGB, $fontfile, $angle);
|
| 323 |
}
|
| 324 |
|
| 325 |
/**
|
| 326 |
* Place text on an image.
|
| 327 |
*
|
| 328 |
* @ingroup imageapi
|
| 329 |
*/
|
| 330 |
function imageapi_gd_image_overlaytext_alpha(&$image, $text, $size = 12, $x = 0, $y = 0, $RGB, $fontfile = 'MgOpenModernaBold', $angle = 0) {
|
| 331 |
$color = imagecolorallocatealpha($image->res, $RGB['red'], $RGB['green'], $RGB['blue'], $RGB['alpha']);
|
| 332 |
$bounds = imagettftext($image->res, $size, $angle, $x, $y, $color, $fontfile, $text);
|
| 333 |
if (empty($bounds)) { return FALSE; }
|
| 334 |
return TRUE;
|
| 335 |
}
|
| 336 |
|
| 337 |
|
| 338 |
/**
|
| 339 |
* UTILITY
|
| 340 |
*/
|
| 341 |
|
| 342 |
/**
|
| 343 |
* Given a font name or path, return the full real path.
|
| 344 |
* Because the toolkit doesn't scan too well, we need to look ahead to avoid
|
| 345 |
* problems and validate.
|
| 346 |
*
|
| 347 |
* Look for the named font file relative to Drupal, the module, and the files
|
| 348 |
* dir.
|
| 349 |
*
|
| 350 |
* @param $fontpath a font file name, eg 'arial.ttf'
|
| 351 |
* @param $strict bool. if set, the func will return nothing on failure. Normal
|
| 352 |
* behaviour is to return the input fontfile name, trusting the system to be
|
| 353 |
* able to find it for you. Strict is used to trigger a validation alert.
|
| 354 |
*
|
| 355 |
* @return the FULL filepath of the font so the image toolkit knows exactly
|
| 356 |
* where it is.
|
| 357 |
*/
|
| 358 |
function textactions_find_font($fontpath, $strict = FALSE) {
|
| 359 |
// Track down the font.
|
| 360 |
if (is_file($fontpath)) {
|
| 361 |
return realpath($fontpath);
|
| 362 |
}
|
| 363 |
|
| 364 |
$fontpath = ltrim($fontpath, '/');
|
| 365 |
// Try local
|
| 366 |
$tryfontpath = drupal_get_path('module', 'imagecache_canvasactions') .'/'. $fontpath;
|
| 367 |
if (is_file($tryfontpath)) {
|
| 368 |
return realpath($tryfontpath);
|
| 369 |
}
|
| 370 |
|
| 371 |
// Try files
|
| 372 |
$tryfontpath = file_create_path($fontpath);
|
| 373 |
if (is_file($tryfontpath)) {
|
| 374 |
return realpath($tryfontpath);
|
| 375 |
}
|
| 376 |
|
| 377 |
if ($strict) return FALSE;
|
| 378 |
// otherwise, just return what we had and hope it's in a system library
|
| 379 |
return $fontpath;
|
| 380 |
}
|