/[drupal]/contributions/modules/textimage/textimage.module
ViewVC logotype

Contents of /contributions/modules/textimage/textimage.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.40 - (show annotations) (download) (as text)
Tue Jun 2 22:26:40 2009 UTC (5 months, 3 weeks ago) by deciphered
Branch: MAIN
CVS Tags: HEAD
Changes since 1.39: +62 -2 lines
File MIME type: text/x-php
#465800: Added backup imagerotate support.
1 <?php
2 // $Id: textimage.module,v 1.39 2009/05/20 00:01:23 deciphered Exp $
3
4 /**
5 * @file
6 */
7
8 /**
9 * Matches all 'P' Unicode character classes (punctuation)
10 */
11 define(
12 'PREG_CLASS_PUNCTUATION',
13 '\x{21}-\x{23}\x{25}-\x{2a}\x{2c}-\x{2f}\x{3a}\x{3b}\x{3f}\x{40}\x{5b}-\x{5d}' .
14 '\x{5f}\x{7b}\x{7d}\x{a1}\x{ab}\x{b7}\x{bb}\x{bf}\x{37e}\x{387}\x{55a}-\x{55f}' .
15 '\x{589}\x{58a}\x{5be}\x{5c0}\x{5c3}\x{5f3}\x{5f4}\x{60c}\x{60d}\x{61b}\x{61f}' .
16 '\x{66a}-\x{66d}\x{6d4}\x{700}-\x{70d}\x{964}\x{965}\x{970}\x{df4}\x{e4f}' .
17 '\x{e5a}\x{e5b}\x{f04}-\x{f12}\x{f3a}-\x{f3d}\x{f85}\x{104a}-\x{104f}\x{10fb}' .
18 '\x{1361}-\x{1368}\x{166d}\x{166e}\x{169b}\x{169c}\x{16eb}-\x{16ed}\x{1735}' .
19 '\x{1736}\x{17d4}-\x{17d6}\x{17d8}-\x{17da}\x{1800}-\x{180a}\x{1944}\x{1945}' .
20 '\x{2010}-\x{2027}\x{2030}-\x{2043}\x{2045}-\x{2051}\x{2053}\x{2054}\x{2057}' .
21 '\x{207d}\x{207e}\x{208d}\x{208e}\x{2329}\x{232a}\x{23b4}-\x{23b6}\x{2768}-' .
22 '\x{2775}\x{27e6}-\x{27eb}\x{2983}-\x{2998}\x{29d8}-\x{29db}\x{29fc}\x{29fd}' .
23 '\x{3001}-\x{3003}\x{3008}-\x{3011}\x{3014}-\x{301f}\x{3030}\x{303d}\x{30a0}' .
24 '\x{30fb}\x{fd3e}\x{fd3f}\x{fe30}-\x{fe52}\x{fe54}-\x{fe61}\x{fe63}\x{fe68}' .
25 '\x{fe6a}\x{fe6b}\x{ff01}-\x{ff03}\x{ff05}-\x{ff0a}\x{ff0c}-\x{ff0f}\x{ff1a}' .
26 '\x{ff1b}\x{ff1f}\x{ff20}\x{ff3b}-\x{ff3d}\x{ff3f}\x{ff5b}\x{ff5d}\x{ff5f}-' .
27 '\x{ff65}'
28 );
29
30 /**
31 * Matches all 'Z' Unicode character classes (separators)
32 */
33 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}');
34
35 define('ALIGN_LEFT', 1);
36 define('ALIGN_CENTER', 2);
37 define('ALIGN_RIGHT', 3);
38
39 /**
40 * Implementation of hook_help().
41 */
42 function textimage_help($path, $arg) {
43 $output = "";
44
45 switch ($path) {
46 case 'admin/help#textimage':
47 $output = '
48 <h2 id="textimage-introduction">The dynamic text to image generator!</h2>
49 <p>
50 Textimage adds text to image functionality using GD2 and Freetype, enabling users to create crisp images on the fly for use as theme objects, headings or limitless other possibilities.
51 </p>
52 <h2>Installing Fonts</h2>
53 <p>
54 Before you can begin using Textimage you must upload at least one TrueType or OpenType font to the server and tell Textimage where you uploaded it. Fonts must have a .tff or .otf extension to be seen by Textimage. If you do not have any TrueType or OpenType fonts, 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>.
55 </p>
56 <h2 id="textimage-configuration">Configuration</h2>
57 <p>
58 The basis of Textimage 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 new presets on the <a href="!presets">presets 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.
59 </p>
60 <p>
61 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:
62 </p>
63 <p>
64 <img src="!example1" alt="example1" />
65 </p>
66 <p>
67 Now we\'ll create a preset called &quot;preset1&quot;. In this preset, set the font to Braggadocio (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, Textimage 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:
68 </p>
69 <p>
70 <img src="!example2" alt="example2" />
71 </p>
72 <p>
73 To get crazy now, create a new preset called &quot;preset2&quot;. In this preset, set the font to Century (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:
74 </p>
75 <p>
76 <img src="!example3" alt="example3" />
77 </p>
78 <p>
79 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.
80 </p>
81 <h2>File Names and Storage</h2>
82 <p>
83 Textimage 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.
84 </p>
85 ';
86
87 $output = t($output, array(
88 '!config' => url('admin/build/textimage/settings'),
89 '!presets' => url('admin/build/textimage/presets'),
90 '!example1' => base_path() . drupal_get_path('module', 'textimage') .'/misc/example1.png',
91 '!example2' => base_path() . drupal_get_path('module', 'textimage') .'/misc/example2.png',
92 '!example3' => base_path() . drupal_get_path('module', 'textimage') .'/misc/example3.png',
93 ));
94 break;
95
96 case 'admin/build/modules#description':
97 case 'admin/build/modules/textimage':
98 case 'admin/build/textimage':
99 $output = t('Provides text to image manipulations.');
100 break;
101 }
102 return $output;
103 }
104
105 /**
106 * Implementation of hook_theme().
107 */
108 function textimage_theme() {
109 $theme = array(
110 'textimage_image' => array(
111 'arguments' => array(
112 'preset',
113 'text',
114 'additional_text' => array(),
115 'format' => 'png',
116 'alt' => '',
117 'title' => '',
118 'attributes' => array(),
119 'getsize' => TRUE,
120 'image' => TRUE,
121 ),
122 ),
123
124 'textimage_preset_edit' => array(
125 'arguments' => array(
126 'form' => array(),
127 ),
128 )
129 );
130
131 module_load_include('inc', 'textimage', 'textimage_admin');
132 foreach (textimage_get_presets() as $preset) {
133 $theme['textimage_formatter_textimage_' . $preset->name] = array(
134 'arguments' => array('element' => NULL),
135 'function' => 'theme_textimage_formatter',
136 );
137 }
138
139 return $theme;
140 }
141
142 /**
143 * Implementation of hook_menu().
144 */
145 function textimage_menu() {
146 $items = array();
147
148 $items[file_directory_path() . '/textimage'] = array(
149 'page callback' => 'textimage_image',
150 'access arguments' => array('access content'),
151 'type' => MENU_CALLBACK,
152 );
153
154 $items['admin/build/textimage'] = array(
155 'title' => 'Textimage',
156 'description' => 'Configure text to image preset functions.',
157 'page callback' => 'textimage_preset_list',
158 'access arguments' => array('administer site configuration'),
159 'type' => MENU_NORMAL_ITEM
160 );
161 $items['admin/build/textimage/preset'] = array(
162 'title' => 'Presets',
163 'type' => MENU_DEFAULT_LOCAL_TASK
164 );
165 $items['admin/build/textimage/preset/list'] = array(
166 'title' => 'List',
167 'type' => MENU_DEFAULT_LOCAL_TASK
168 );
169
170 $items['admin/build/textimage/preset/new'] = array(
171 'title' => 'New',
172 'page callback' => 'drupal_get_form',
173 'page arguments' => array('textimage_preset_edit', 'new'),
174 'access arguments' => array('administer site configuration'),
175 'weight' => 1,
176 'type' => MENU_LOCAL_TASK
177 );
178
179 $items['admin/build/textimage/preset/%/edit'] = array(
180 'title' => 'Edit Preset',
181 'page callback' => 'drupal_get_form',
182 'page arguments' => array('textimage_preset_edit', 'edit', 4),
183 'access arguments' => array('administer site configuration'),
184 'type' => MENU_CALLBACK,
185 );
186
187 $items['admin/build/textimage/preset/%/delete'] = array(
188 'title' => 'Edit Preset',
189 'load arguments' => array(4),
190 'page callback' => 'drupal_get_form',
191 'page arguments' => array('textimage_preset_delete_confirm', 4),
192 'access arguments' => array('administer site configuration'),
193 'type' => MENU_CALLBACK,
194 );
195
196 $items['admin/build/textimage/preset/%/flush'] = array(
197 'title' => 'Flush Preset Cache',
198 'load arguments' => array(4),
199 'page callback' => 'drupal_get_form',
200 'page arguments' => array('textimage_preset_flush_confirm', 4),
201 'access arguments' => array('administer site configuration'),
202 'type' => MENU_CALLBACK,
203 );
204
205 $items['admin/build/textimage/settings'] = array(
206 'title' => 'Settings',
207 'page callback' => 'drupal_get_form',
208 'page arguments' => array('textimage_settings_form'),
209 'access arguments' => array('administer site configuration'),
210 'weight' => 0,
211 'type' => MENU_LOCAL_TASK
212 );
213
214 $items['js/textimage'] = array(
215 'page callback' => 'textimage_js',
216 'access arguments' => array('access content'),
217 'type' => MENU_CALLBACK,
218 );
219
220 return $items;
221 }
222
223 /**
224 * Implementation of hook_perm().
225 */
226 function textimage_perm() {
227 return array(
228 'create textimages' => array(
229 'title' => t('Create Textimages'),
230 'description' => t('Create Textimages'),
231 )
232 );
233 }
234
235 /**
236 * Menu Callback function converts the current textimage path into an image. On
237 * first request, the image is returned to the browser from Drupal and then
238 * saved to the disk. On subsequent requests (with Clean URLs enabled), the
239 * cached image is loaded directly.
240 *
241 * This function takes a dynamic number of arguments.
242 *
243 * @param $preset_name
244 * The name of the preset to be used in this textimage
245 * @param ...
246 * An unlimited number of additional text parameters to be used as the
247 * display text for textimages displayed on top of one another. Only used
248 * if the current preset has the its Background Image option set to the
249 * result of another preset. Text is used in reverse order. So the last
250 * directory will be the first chained preset used.
251 * @param $text
252 * The text to be displayed in this preset with the output format
253 * appended as the file extension. For example, 'sample.png' will output a
254 * PNG with the text 'sample'. 'sample.jpg' will output the same image but
255 * in JPG format.
256 *
257 */
258 function textimage_image() {
259 $pattern = '/' . str_replace('/', '\/', base_path() . '(\?q=)?' . file_directory_path() . '/textimage/') . '/';
260 $args = explode('/', preg_replace($pattern, '', request_uri()));
261
262 $preset = array_shift($args);
263 if (is_numeric($preset)) {
264 drupal_not_found();
265 exit;
266 }
267
268 $filename = urldecode(array_pop($args));
269 $additional_text = $args;
270
271 // Determine our output format
272 preg_match('/\.([a-z]+)$/i', $filename, $matches);
273 $format = $matches[1];
274 if ($format == 'jpg') {
275 $format = 'jpeg';
276 }
277
278 // Determine the text to display
279 $text = preg_replace('/\.([a-z]+)$/i', '', $filename);
280
281 if (!$img = textimage_build_image('url', $preset, $text, $additional_text, $format)) {
282 return FALSE;
283 }
284
285 drupal_goto($img);
286 }
287
288 function textimage_build_image($method, $preset, $text, $additional_text = array(), $format = 'png') {
289 // Integrety check
290 $output_function = 'image' . $format;
291 if (!drupal_function_exists($output_function)) {
292 $message = t('Unable to generate Textimage as the file extension is unsupported on this system. Files must have a .png, .jpg, or .gif extension.');
293 watchdog('textimage', $message, WATCHDOG_ERROR);
294 drupal_set_message($message, 'error');
295 return FALSE;
296 }
297
298 // Load preset
299 if (!is_array($preset) && !$preset = _textimage_preset_load($preset)) {
300 $message = t('Unable to generate Textimage as the preset %preset is not defined.', array('%preset' => $preset));
301 watchdog('textimage', $message, WATCHDOG_ERROR);
302 drupal_set_message($message, 'error');
303 return FALSE;
304 }
305
306 $result = db_query(
307 "SELECT file FROM {textimage_image} WHERE pid = :pid AND data = :data",
308 array(
309 ':pid' => $preset['pid'],
310 ':data' => serialize(array('format' => $format, 'text' => $text, 'additional_text' => $additional_text))
311 )
312 );
313
314 $result = db_fetch_object($result);
315 if ((!$result && (user_access('create textimages') || $method == 'theme')) || $preset['pid'] == 0) {
316 // Generate the image resource
317 $img = textimage_image_from_preset($preset, $text, $additional_text);
318
319 $filename = REQUEST_TIME . rand(1000, 9999) . '.' . $format;
320 $directory = file_directory_path() . '/textimage/' . $preset['pid'];
321
322 // Save the result so we don't have to recreate
323 textimage_directory_check($directory);
324 $output_function($img, $directory . '/' . $filename);
325 imagedestroy($img);
326
327 if ($preset['pid'] !== 0) {
328 db_query(
329 "INSERT INTO {textimage_image} (pid, file, data) VALUES ('%s', '%s', '%s')",
330 $preset['pid'], $directory . '/' . $filename,
331 serialize(array('format' => $format, 'text' => $text, 'additional_text' => $additional_text))
332 );
333 }
334
335 return $directory . '/' . $filename;
336 }
337
338 elseif (!$result) {
339 drupal_access_denied();
340 return FALSE;
341 }
342
343 else {
344 return $result->file;
345 }
346 }
347
348 /**
349 * Loads the Textimage preset and generates the GD image resource.
350 *
351 * @param $pid
352 * The id of the preset to be used in this textimage
353 * @param $text
354 * The text to be displayed in this preset
355 * @param $additional_text
356 * An array of text to be used in subsequent textimages. Only used if this
357 * preset uses the result of another preset as its background image.
358 */
359 function textimage_image_from_preset($preset, $text, $additional_text = array()) {
360 foreach ($preset['settings'] as $key => $settings) {
361 if (is_array($settings)) {
362 foreach ($settings as $name => $value) {
363 ${$key . '_' . $name} = $value;
364 }
365 }
366 }
367 $text_fixed_width = isset($text_fixed_width) ? $text_fixed_width : 0;
368
369 // Set font path
370 $font_file = variable_get('textimage_fonts_path', drupal_get_path('module', 'textimage') . '/fonts') . '/' . $font_file;
371
372 // Convert text case
373 switch ($text_case) {
374 case 'upper':
375 $text = drupal_strtoupper($text);
376 break;
377
378 case 'lower':
379 $text = drupal_strtolower($text);
380 break;
381
382 case 'ucfirst':
383 $text = drupal_ucfirst($text);
384 break;
385
386 case 'ucwords':
387 $text = preg_replace('/\s(\w+)\b/e', "drupal_ucfirst('$1')", $text);
388 break;
389 }
390
391 // Generate the textimage
392 $img = textimage_text_to_image($text, $font_size, $font_file, $font_color, $text_angle, $text_maximum_width, $text_fixed_width, $text_align);
393
394 // Add a border
395 if ($text_stroke['width'] && $text_stroke['color']) {
396 $img = textimage_image_add_stroke($img, $text_stroke['width'], $text_stroke['color'], $font_color['opacity']);
397 }
398
399 // Add margin
400 if ($text_margin['top'] || $text_margin['right'] || $text_margin['bottom'] || $text_margin['left']) {
401 $img = textimage_image_add_margin($img, $text_margin);
402 }
403
404 // Place result on top of another preset's result
405 if (is_numeric($background_image) && $background_image = _textimage_preset_load($background_image)) {
406 $next_preset = $background_image;
407 $next_text = array_pop($additional_text);
408 if (empty($next_text)) {
409 $next_text = $text;
410 }
411 $background_resource = textimage_image_from_preset($next_preset, $next_text, $additional_text);
412
413 $text_width = imagesx($img);
414 $text_height = imagesy($img);
415 imagealphablending($background_resource, TRUE);
416 imagecopy($background_resource, $img, $background_xoffset, $background_yoffset, 0, 0, $text_width, $text_height);
417 imagesavealpha($background_resource, TRUE);
418 $img = $background_resource;
419 }
420
421 // Place result on background image if available
422 elseif (is_file($background_image)) {
423 $info = image_get_info($background_image);
424 $background_resource = image_toolkit_invoke('open', array($background_image, $info['extension']));
425
426 $text_width = imagesx($img);
427 $text_height = imagesy($img);
428 imagealphablending($background_resource, TRUE);
429 imagecopy($background_resource, $img, $background_xoffset, $background_yoffset, 0, 0, $text_width, $text_height);
430 imagesavealpha($background_resource, TRUE);
431 $img = $background_resource;
432 }
433
434 else {
435 $background_resource = imagecreatetruecolor(imagesx($img), imagesy($img));
436
437 $alpha = 0;
438 if (!$background_color) {
439 $alpha = 127;
440 $background_color = '#FFFFFF';
441 }
442
443 list($r, $g, $b) = _textimage_hex2rgb($background_color);
444 $back = imagecolorallocatealpha($background_resource, $r, $g, $b, $alpha);
445 imagefill($background_resource, 0, 0, $back);
446
447 imagealphablending($background_resource, TRUE);
448 imagecopy($background_resource, $img, 0, 0, 0, 0, imagesx($img), imagesy($img));
449 imagesavealpha($background_resource, TRUE);
450 $img = $background_resource;
451 }
452
453 return $img;
454 }
455
456 /**
457 * This function adds a margin (or border) around an existing image resource
458 */
459 function textimage_image_add_margin($img, $margin) {
460 $width = imagesx($img);
461 $height = imagesy($img);
462
463 // Create a new image for the background
464 $back_img = imagecreatetruecolor($width + $margin['right'] + $margin['left'], $height + $margin['top'] + $margin['bottom']);
465 $back = imagecolorallocatealpha($back_img, 0, 0, 0, 127);
466 imagefill($back_img, 0, 0, $back);
467
468 // Apply the source image ontop the background with the new margin
469 imagecopy($back_img, $img, $margin['left'], $margin['top'], 0, 0, $width, $height);
470 imagealphablending($back_img, TRUE);
471 imagesavealpha($back_img, TRUE);
472
473 return $back_img;
474 }
475
476 /**
477 * Stroke function adds a solid color stroke around an image with a transparent
478 * background.
479 *
480 * @param $img
481 * The gd image resource of the image to modify
482 * @param $thickness
483 * The width of the stroke to apply
484 * @param $color
485 * The color of the stroke to apply
486 *
487 * @todo Add $position parameter to allow the stroke to be applied 'inside',
488 * 'middle', or 'outside'. outside is the only current behavior.
489 */
490 function textimage_image_add_stroke($img, $thickness, $color, $text_opacity) {
491 if ($thickness > 0) {
492 $width = imagesx($img);
493 $height = imagesy($img);
494
495 // Create a new image which we'll lay over the original
496 $border_img = imagecreatetruecolor($width, $height);
497 $back = imagecolorallocatealpha($border_img, 0, 0, 0, 127);
498 imagefill($border_img, 0, 0, $back);
499
500 for ($x = 0; $x < $width; $x++) {
501 for ($y = 0; $y < $height; $y++) {
502 $c = imagecolorsforindex($img, imagecolorat($img, $x, $y ));
503 // Outside only modify pixels which are less opaque than the text opacity.
504 if ($c['alpha'] > (-($text_opacity - 100) / 100 * 127)) {
505 textimage_image_stroke_change_pixels($img, $border_img, $thickness, $color, $x, $y, $width, $height);
506 }
507 }
508 }
509
510 // Merge the images
511 imagealphablending($img, TRUE);
512 imagecopy($img, $border_img, 0, 0, 0, 0, $width, $height);
513 }
514
515 return $img;
516 }
517
518 /**
519 * Utility function for image_stroke. Analyzes surrounding pixels and determines
520 * opacity of a pixel at that x-y coordinate
521 */
522 function textimage_image_stroke_change_pixels(&$img, &$border_img, $thickness, $color, $x, $y, $width, $height) {
523 list($r, $g, $b) = _textimage_hex2rgb($color);
524
525 $pixel = imagecolorsforindex($img, imagecolorat($img, $x, $y));
526
527 // Preform a radial analysis of all pixels within the radius of $thickness pixels
528 $degree_increment = (90 / $thickness);
529 $radial_coords = array();
530
531 for ($degrees = 0; $degrees <= 90; $degrees += $degree_increment) {
532 $x_offset = round(cos($degrees) * $thickness);
533 $y_offset = round(sin($degrees) * $thickness);
534
535 // Add the coordinates for the corresponding pixel in each 90° quadrant
536 $radial_coords[] = array('x' => $x + $x_offset, 'y' => $y + $y_offset);
537 $radial_coords[] = array('x' => $x - $x_offset, 'y' => $y + $y_offset);
538 $radial_coords[] = array('x' => $x + $x_offset, 'y' => $y - $y_offset);
539 $radial_coords[] = array('x' => $x - $x_offset, 'y' => $y - $y_offset);
540 }
541
542 // Generate a total alpha level for all analyzed pixels
543 $total_alpha = 0;
544 $total_colors = 0;
545
546 foreach ($radial_coords as $coords) {
547 if ($coords['x'] >= 0 && $coords['y'] >= 0 && $coords['x'] < $width && $coords['y'] < $height) {
548 $xy_color = imagecolorsforindex($img, imagecolorat($img, $coords['x'], $coords['y']));
549 }
550 else {
551 // This analized pixel is outside the dimensions of the image, record as transparent
552 $xy_color = array('alpha' => '127');
553 }
554
555 $total_alpha += $xy_color['alpha'];
556 $total_colors++;
557 }
558
559 // Check that we're not in the middle of the image or in a blonk area
560 if ($total_alpha < (127 * $total_colors) && $total_alpha > 0) {
561 // If we're on a semi-transparent pixel, blend the remaining amount with our border color
562 if ($pixel['alpha'] < 127) {
563 $alpha = 127 - $pixel['alpha'];
564 }
565 // We're on a completely transparent pixel where we'll use a generated transparency
566 else {
567 $alpha = 127 - ((127 * $total_colors) - $total_alpha);
568 }
569 $alpha = ($alpha < 0) ? 0 : $alpha;
570 $alpha = ($alpha > 127) ? 127 : $alpha;
571 // Apply the color to the border overlay image
572 $color = imagecolorallocatealpha($border_img, $r, $g, $b, $alpha);
573 imagesetpixel($border_img, $x, $y, $color);
574 }
575 }
576
577 /**
578 * Create the image directory relative to the 'files' dir - if user specified one
579 * Won't allow form submit unless the directory exists & is writable
580 *
581 * @param $directory_path
582 * String containing the path of the directory to check.
583 */
584 function textimage_directory_check($directory_path) {
585 // create each directory necessary if it doesn't exist
586 $directory_path = drupal_substr($directory_path, drupal_strlen(file_directory_path()) + 1);
587 foreach (explode('/', $directory_path) as $dir) {
588 $dirs[] = $dir;
589 $dir = file_directory_path() . '/' . implode($dirs, '/');
590 if (!file_check_directory($dir, FILE_CREATE_DIRECTORY)) {
591 return FALSE;
592 }
593 }
594 return TRUE;
595 }
596
597 /**
598 * Helper function for wrapping text (measures width).
599 */
600 function textimage_measure_text_width($text, $fontsize, $font) {
601 $box = imagettfbbox($fontsize, 0, $font, $text);
602 return abs($box[4] - $box[0]) + 4;
603 }
604
605 /**
606 * Wrap text for rendering at a given width.
607 */
608 function textimage_wrap_text($text, $fontsize, $font, $maximum_width) {
609 // State variables for the search interval
610 $end = 0;
611 $begin = 0;
612 $fit = $begin;
613
614 // Note: we count in bytes for speed reasons, but maintain character boundaries.
615 while (true) {
616 // Find the next wrap point (always after trailing whitespace).
617 if (drupal_preg_match('/[' . PREG_CLASS_PUNCTUATION . '][' . PREG_CLASS_SEPARATOR . ']*|[' . PREG_CLASS_SEPARATOR . ']+/u', $text, $match, PREG_OFFSET_CAPTURE, $end)) {
618 $end = $match[0][1] + drupal_strlen($match[0][0]);
619 }
620 else {
621 $end = drupal_strlen($text);
622 }
623
624 // Fetch text, removing trailing white-space and measure it.
625 $line = preg_replace('/[' . PREG_CLASS_SEPARATOR . ']+$/u', '', drupal_substr($text, $begin, $end - $begin));
626 $width = textimage_measure_text_width($line, $fontsize, $font);
627
628 // See if $line extends past the available space.
629 if ($width > $maximum_width) {
630 // If this is the first word, we need to truncate it.
631 if ($fit == $begin) {
632 // Cut off letters until it fits.
633 while (drupal_strlen($line) > 0 && $width > $maximum_width) {
634 $line = drupal_substr($line, 0, -1);
635 $width = textimage_measure_text_width($line, $fontsize, $font);
636 }
637 // If no fit was found, the image is too narrow..
638 $fit = drupal_strlen($line) ? $begin + drupal_strlen($line) : $end;
639 }
640
641 // We have a valid fit for the next line. Insert a line-break and reset
642 // the search interval.
643 $text = drupal_substr($text, 0, $fit) . "\n" . drupal_substr($text, $fit);
644 $end = $begin = ++$fit;
645 }
646 else {
647 // We can fit this text. Wait for now.
648 $fit = $end;
649 }
650
651 if ($end == drupal_strlen($text)) {
652 // All text fits. No more changes are needed.
653 break;
654 }
655 }
656 return $text;
657 }
658
659 /**
660 * Unicode-safe preg_match().
661 *
662 * Search subject for a match to the regular expression given in pattern,
663 * but return offsets in characters, where preg_match would return offsets
664 * in bytes.
665 *
666 * @see http://php.net/manual/en/function.preg-match.php
667 */
668 if (!function_exists('drupal_preg_match')) {
669 function drupal_preg_match($pattern, $subject, &$matches, $flags = NULL, $offset = 0) {
670 // Convert the offset value from characters to bytes.
671 $offset = strlen(drupal_substr($subject, 0, $offset, $encoding));
672
673 $return_value = preg_match($pattern, $subject, $matches, $flags, $offset);
674
675 if ($return_value && ($flags & PREG_OFFSET_CAPTURE)) {
676 foreach ($matches as &$match) {
677 // Convert the offset returned by preg_match from bytes back to characters.
678 $match[1] = drupal_strlen(substr($subject, 0, $match[1]));
679 }
680 }
681 return $return_value;
682 }
683 }
684
685 /**
686 * Generate an image containing text with the given parameters.
687 *
688 * @return $image
689 * A GD image resource.
690 */
691 function textimage_text_to_image($text, $fontsize, $font, $color = array('hex' => '#000000', 'opacity' => '100'), $angle = 0, $maximum_width = 0, $fixed_width = 0, $align = ALIGN_LEFT) {
692 // Set rotation angle.
693 $q_angle = -$angle;
694 while ($q_angle > 0 || $q_angle <= -90) {
695 $q_angle -= $q_angle > 0 ? 90 : -90;
696 }
697 while ($angle < 0 || $angle >= 360) {
698 $angle += $angle < 0 ? 360 : -360;
699 }
700 $rotation = -(floor($angle / 90) * 90);
701
702 $rad = deg2rad($q_angle);
703 $sin = -sin($rad);
704 $cos = cos($rad);
705
706 // Perform text wrapping, if necessary.
707 if ($maximum_width - 1 > 0) {
708 $text = textimage_wrap_text($text, $fontsize, $font, $maximum_width - 1);
709 }
710
711 // Get co-ordinates of text string and boundry box.
712 $coords = imagettfbbox($fontsize, 0, $font, $text);
713
714 // Set consistant heights.
715 if ($coords[3] - $coords[5] < $fontsize * 2) {
716 $characters = drupal_map_assoc(range(33, 122), 'chr');
717 $characters = join($characters);
718 $testcoords = imagettfbbox($fontsize, 0, $font, $characters);
719
720 if ($coords[3] - $coords[5] < $testcoords[3] - $testcoords[5]) {
721 $coords[1] = $testcoords[1];
722 $coords[3] = $testcoords[3];
723 $coords[5] = $testcoords[5];
724 $coords[7] = $testcoords[7];
725 }
726 }
727
728 // Fix boundary box.
729 $bbox = array();
730 for ($i = 0; $i < 7; $i += 2) {
731 $bbox[$i] = round($coords[$i] * $cos + $coords[$i + 1] * -$sin);
732 $bbox[$i + 1] = round($coords[$i + 1] * $cos - $coords[$i] * -$sin);
733 }
734
735 // Calculate dimensions of text box.
736 $text_width = sqrt(pow(abs($bbox[0] - $bbox[2]), 2) + pow(abs($bbox[1] - $bbox[3]), 2));
737 $text_height = sqrt(pow(abs($bbox[0] - $bbox[6]), 2) + pow(abs($bbox[1] - $bbox[7]), 2)); // Not used.
738
739 // Calculate dimensions of box from text box.
740 $box_width = max($bbox[0], $bbox[2], $bbox[4], $bbox[6]) - min($bbox[0], $bbox[2], $bbox[4], $bbox[6]);
741 $box_height = max($bbox[1], $bbox[3], $bbox[5], $bbox[7]) - min($bbox[1], $bbox[3], $bbox[5], $bbox[7]);
742
743 // Calculate dimensions of image.
744 $image_width = ($fixed_width && $maximum_width > 0)
745 ? ($maximum_width - 1) * $cos + $text_height * $sin
746 : $box_width;
747 $image_height = ($fixed_width && $maximum_width > 0)
748 ? ($maximum_width - 1) * $sin + $text_height * $cos
749 : $box_height;
750
751 // Create Image.
752 $image = imagecreatetruecolor($image_width + 1, $image_height + 1);
753 $back = imagecolorallocatealpha($image, 0, 0, 0, 127);
754 imagefill($image, 0, 0, $back);
755
756 // Set text alignment left
757 $x = -$bbox[0];
758 $y = $box_height - $bbox[3];
759
760 if (($fixed_width && $maximum_width > 0) && $align !== ALIGN_LEFT) {
761 switch ($align) {
762
763 // Set text alignment center
764 case ALIGN_CENTER:
765 $x += ($image_width - $box_width) / 2;
766 $y += ($image_height - $box_height) / 2;
767 break;
768
769 // Set text alignment right
770 case ALIGN_RIGHT:
771 $x += $image_width - $box_width;
772 $y += $image_height - $box_height;
773 break;
774 }
775 }
776
777 // Create the textimage.
778 list($r, $g, $b) = _textimage_hex2rgb($color['hex']);
779 $alpha = -($color['opacity'] - 100) / 100 * 127;
780 $fore = imagecolorallocatealpha($image, $r, $g, $b, $alpha);
781
782 imagettftext($image, $fontsize, $q_angle, $x, $y, $fore, $font, $text);
783 if ($rotation != 0) {
784 $image = imagerotate($image, $rotation, 0);
785 }
786
787 imagealphablending($image, TRUE);
788 imagesavealpha($image, TRUE);
789
790 return $image;
791 }
792
793 if (!function_exists('imagerotate')) {
794 function imagerotate($im, $angle, $bgcolor) {
795 if ($angle === 0) {
796 return $im;
797 }
798 // imagerotate() in php's libgd rotates the image counterclockwise,
799 // this implementation rotates clockwise. The angle needs to be
800 // inverted to give the same behaviour between these implementations.
801 $angle = 360 + $angle;
802
803 $width = imagesx($im);
804 $height = imagesy($im);
805 // background color.
806 list($r, $g, $b, $a) = _textimage_hex2rgb($bgcolor);
807
808 switch ($angle) {
809 case 270:
810 case 90:
811 // flip dimensions.
812 $rot_width = $height;
813 $rot_height = $width;
814 break;
815 case 180:
816 // maintain dims.
817 $rot_width = $width;
818 $rot_height = $height;
819 break;
820 }
821
822 $rotate = imagecreatetruecolor($rot_width, $rot_height);
823 $bg = imagecolorallocatealpha($rotate, $r, $g, $b, $a);
824 imagefilledrectangle($rotate, 0, 0, $rot_width, $rot_height, $bg);
825 imagealphablending($rotate, FALSE);
826 imagesavealpha($rotate, TRUE);
827
828 switch ($angle) {
829 case 270:
830 $rot_width--;
831 for ($y = 0; $y < $height; ++$y)
832 for ($x = 0; $x < $width; ++$x)
833 imagesetpixel($rotate, $rot_width - $y, $x, imagecolorat($im, $x, $y));
834 break;
835 case 90:
836 $rot_height--;
837 for ($y = 0; $y < $height; ++$y)
838 for ($x = 0; $x < $width; ++$x)
839 imagesetpixel($rotate, $y, $rot_height - $x, imagecolorat($im, $x, $y));
840 break;
841 case 180:
842 $rot_width--;
843 $rot_height--;
844 for ($y = 0; $y < $height; ++$y)
845 for ($x = 0; $x < $width; ++$x)
846 imagesetpixel($rotate, $rot_width - $x, $rot_height - $y, imagecolorat($im, $x, $y));
847 break;
848 }
849 return $rotate;
850 }
851 }
852
853 /**
854 * load a preset by id or name.
855 * @param preset
856 * preset id or name.
857 */
858 function _textimage_preset_load($preset) {
859 // Load preset by id
860 if (is_numeric($preset)) {
861 $preset = db_fetch_array(
862 db_query('SELECT * FROM {textimage_preset} WHERE pid = %d', $preset)
863 );
864 }
865
866 // Load preset by name
867 else {
868 $preset = db_fetch_array(
869 db_query("SELECT * FROM {textimage_preset} WHERE name = '%s'", $preset)
870 );
871 }
872
873 if (empty($preset)) {
874 return FALSE;
875 }
876
877 else {
878 $preset['settings'] = unserialize($preset['settings']);
879 return $preset;
880 }
881 }
882
883 /**
884 * Convert a hex color representation to it's rgb integer components.
885 *
886 * @param $hex
887 * Hex representation of the color.
888 * Can be in the formats: '#ABC','ABC','#AABBCC','AABBCC'
889 * @return
890 * Array with three components RGB.
891 */
892 function _textimage_hex2rgb($hex) {
893 $r = $g = $b = '';
894
895 $hex = ltrim($hex, '#');
896 if (preg_match('/^[0-9a-f]{3}$/i', $hex)) {
897 // 'FA3' is the same as 'FFAA33' so r=FF, g=AA, b=33
898 $r = str_repeat($hex{0}, 2);
899 $g = str_repeat($hex{1}, 2);
900 $b = str_repeat($hex{2}, 2);
901 }
902 elseif (preg_match('/^[0-9a-f]{6}$/i', $hex)) {
903 // #FFAA33 or r=FF, g=AA, b=33
904 $r = drupal_substr($hex, 0, 2);
905 $g = drupal_substr($hex, 2, 2);
906 $b = drupal_substr($hex, 4, 2);
907 }
908
909 $r = hexdec($r);
910 $g = hexdec($g);
911 $b = hexdec($b);
912 return array($r, $g, $b);
913 }
914
915 /**
916 * Implementation of hook_field_formatter_info().
917 */
918 function textimage_field_formatter_info() {
919 $formatters = array();
920
921 module_load_include('inc', 'textimage', 'textimage_admin');
922 foreach (textimage_get_presets() as $preset) {
923 $formatters['textimage_' . $preset->name] = array(
924 'label' => t('Textimage:') . ' ' . $preset->name,
925 'field types' => array('text', 'email'),
926 );
927 }
928
929 return $formatters;
930 }
931
932 function theme_textimage_formatter($element) {
933 $alt = $title = $element['#item']['safe'];
934 if (isset($element['#item']['email'])) {
935 $alt = $title = '';
936 }
937
938 return theme('textimage_image', drupal_substr($element['#formatter'], 10), $element['#item']['safe'], array(), 'png', $alt, $title);
939 }
940
941 /**
942 * Theme function for displaying textimages
943 */
944 function theme_textimage_image($preset, $text, $additional_text = array(), $format = 'png', $alt = '', $title = '', $attributes = array(), $getsize = TRUE, $image = TRUE) {
945 if (!$path = textimage_build_image('theme', $preset, $text, $additional_text, $format)) {
946 return FALSE;
947 }
948 if ($image) {
949 return theme('image', $path, $alt, $title, $attributes, $getsize);
950 }
951 return $path;
952 }
953
954 /**
955 * Textimage AHAH functionality
956 */
957 function textimage_js() {
958 $output = '';
959
960 switch (arg(2)) {
961 case 'preview':
962 textimage_js_preview($output);
963 break;
964
965 case 'background':
966 textimage_js_background($output);
967 break;
968 }
969
970 print drupal_json($output);
971 exit;
972 }
973
974 function textimage_js_preview(&$output) {
975 $text = $_POST['settings']['preview']['text']['default'];
976
977 $additional_text = array();
978 if (isset($_POST['settings']['preview']['text']['additional'])) {
979 $additional_text = $_POST['settings']['preview']['text']['additional'];
980 rsort($additional_text);
981 }
982
983 $preset = array(
984 'pid' => 0,
985 'name' => $_POST['name'],
986 'settings' => $_POST['settings'],
987 );
988
989 $output = theme_textimage_image($preset, $text, $additional_text, 'png', $text, $text);
990 }
991
992 function textimage_js_background(&$output) {
993 if (empty($_POST['form_build_id'])) {
994 // Invalid request.
995 drupal_set_message(t('An unrecoverable error occurred.'), 'error');
996 print drupal_to_js(array('data' => theme('status_messages')));
997 exit;
998 }
999
1000 // Build the new form.
1001 $form_state = array('submitted' => FALSE);
1002 $form_build_id = $_POST['form_build_id'];
1003 $form = form_get_cache($form_build_id, $form_state);
1004
1005 if (!$form) {
1006 // Invalid form_build_id.
1007 drupal_set_message(t('An unrecoverable error occurred. This form was missing from the server cache. Try reloading the page and submitting again.'), 'error');
1008 print drupal_to_js(array('data' => theme('status_messages')));
1009 exit;
1010 }
1011
1012 // form_get_cache() doesn't yield the original $form_state,
1013 // but form_builder() does. Needed for retrieving the file array.
1014 $built_form = $form;
1015 $built_form_state = $form_state;
1016
1017 $built_form += array('#post' => $_POST);
1018 $built_form = form_builder($_POST['form_id'], $built_form, $built_form_state);
1019
1020 // Clean ids, so that the same element doesn't get a different element id
1021 // when rendered once more further down.
1022 form_clean_id(NULL, TRUE);
1023
1024 foreach (element_children($form['settings']['preview']['text']) as $key) {
1025 if ($key !== 'default') {
1026 unset($form['settings']['preview']['text'][$key]);
1027 }
1028 }
1029 if (is_numeric($_POST['settings']['background']['image']) && $preset = _textimage_preset_load($_POST['settings']['background']['image'])) {
1030 if (!isset($form['settings']['preview']['text']['additional'])) {
1031 $form['settings']['preview']['text']['additional'] = array();
1032 }
1033 _textimage_js_background($form);
1034
1035 while (is_numeric($preset['settings']['background']['image']) && $preset = _textimage_preset_load($preset['settings']['background']['image'])) {
1036 _textimage_js_background($form);
1037 }
1038 }
1039
1040 form_set_cache($form_build_id, $form, $form_state);
1041 $form += array(
1042 '#post' => $_POST,
1043 '#programmed' => FALSE,
1044 );
1045 $form = form_builder($_POST['form_id'], $form, $form_state);
1046
1047 $output = drupal_render($form['settings']['preview']['text']);
1048 }
1049
1050 function _textimage_js_background(&$form) {
1051 $id = count($form['settings']['preview']['text']['additional']);
1052
1053 $form['settings']['preview']['text']['additional'][$id] = $form['settings']['preview']['text']['default'];
1054 $form['settings']['preview']['text']['additional'][$id]['#title'] = 'Additional text';
1055 }

  ViewVC Help
Powered by ViewVC 1.1.2