image_effects_text.inc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. <?php
  2. /**
  3. * Implementation of image_effects_text.
  4. */
  5. /**
  6. * Real implementation of hook_help() called by image_effects_text_help().
  7. *
  8. * Experimental diagnostic page to assist locating valid fonts on the system.
  9. * Only tuned for Ubuntu so far. I've been unable do find ubiquitous tools that
  10. * provide useful font listings.'
  11. */
  12. function image_effects_text_help_inc(/*$path, $arg*/) {
  13. $output = "<p>
  14. For text rendering to work on a server, we <em>must</em>
  15. know the system path to the font <em>file</em>, not just the name.
  16. Font library handling differs too much on different systems and
  17. the available PHP toolkits are unable to return good diagnostics.
  18. </p><p>
  19. On Debian/Ubuntu, you may find your fonts in and under
  20. <code>/usr/share/fonts/truetype/</code>
  21. eg <code>'/usr/share/fonts/truetype/ttf-bitstream-vera/VeraMono.ttf'</code>
  22. </p><p>
  23. On OSX, they are probably in <code>/Library/Fonts/</code>
  24. eg <code>'/Library/Fonts/Times New Roman Bold Italic.ttf'</code>
  25. </p><p>
  26. On Windows, they are probably in <code>C:\\WINDOWS\Fonts\</code>
  27. eg <code>'C:\\WINDOWS\\Fonts\\comic.ttf'</code>
  28. </p><p>
  29. Of course, this will change if you deploy to a different server!
  30. so the best approach is to place your own TTF font file inside your private
  31. or public files directory and use that. Just give the filename with the
  32. 'private://' or 'public://' scheme prefix and it should be found.
  33. </p>
  34. ";
  35. $output .= t("<p>Files directory is !files</p>", array('!files' => variable_get('file_public_path', conf_path() . '/files')));
  36. return $output;
  37. }
  38. /**
  39. * Image effect form callback for the text effect.
  40. *
  41. * Note that this is not a complete form, it only contains the portion of the
  42. * form for configuring the effect options. Therefore it does not not need to
  43. * include metadata about the effect, nor a submit button.
  44. *
  45. * @param array $data
  46. * The current configuration for this image effect.
  47. *
  48. * @return array
  49. * The form definition for this effect.
  50. */
  51. function image_effects_text_form_inc(array $data) {
  52. // Use of functions imagecache_file_...() creates a dependency on file utility.inc.
  53. module_load_include('inc', 'imagecache_actions', 'utility');
  54. // Use of function imagecache_rgb_form() creates a dependency on file utility-color.inc.
  55. module_load_include('inc', 'imagecache_actions', 'utility-color');
  56. // Note: we also need to check for the existence of the module: admin has
  57. // all rights, so user_access(...) returns TRUE even if the module is not
  58. // enabled and the permission does not exist.
  59. // A user without the 'use PHP for settings' permission (defined by the core
  60. // PHP filter module) may not:
  61. // - Select the 'PHP code' text source option if it is currently not selected.
  62. // - Change the 'PHP code' textarea.
  63. $allow_php = module_exists('php') && user_access('use PHP for settings');
  64. $defaults = array(
  65. 'text_source' => 'text',
  66. 'text' => t('Hello World!'),
  67. 'php' => 'if (!$image_context[\'entity\']) {
  68. return \'' . t('No referring entity') . '\';
  69. }
  70. $entity_type = \'node\';
  71. $field_name = \'my_field\';
  72. $entity = $image_context[\'entity\'];
  73. $field = field_get_items($entity_type, $entity, $field_name);
  74. if ($field) {
  75. return isset($field[0][\'value\']) ? $field[0][\'value\'] : \'' . t('No field value') . '\';
  76. }
  77. ',
  78. 'text_case' => 'none',
  79. 'fontfile' => drupal_get_path('module', 'image_effects_text') . '/Komika_display.ttf',
  80. 'size' => 50,
  81. 'RGB' => array('HEX' => '#000000'),
  82. 'alpha' => 100,
  83. 'xpos' => '0',
  84. 'ypos' => '0',
  85. 'halign' => 'left',
  86. 'valign' => 'top',
  87. 'angle' => 0,
  88. );
  89. $data += $defaults;
  90. $tokens = token_info();
  91. $tokens = array_keys($tokens['types']);
  92. $form = array(
  93. 'text_help' => array(
  94. '#type' => 'item',
  95. '#title' => t('Text'),
  96. '#description' => t('<p>Select the source of the text:</p>
  97. <ul>
  98. <li><strong>Image alt</strong>: the alt text of an image field referring to this image is taken.</li>
  99. <li><strong>Image title</strong>: the title text of an image field referring to this image is taken.</li>
  100. <li><strong>Text (with token replacement)</strong>: A text with optional token replacement. Line breaks can be inserted with \n (\\\\n for a literal \n). You can define the text in the text field below the drop down.</li>
  101. <li><strong>PHP code</strong>: a piece of PHP code that returns the text to display. You can define the PHP code in the text area below the drop down. You will need the \'%use_php\' permission, defined by the \'PHP filter\' module.</li>
  102. </ul>
  103. <p>See the help in README.txt for an extensive explanation of the possibilities.</p>',
  104. array('%use_php' => t('Use PHP for settings'))),
  105. ),
  106. 'text_source' => array(
  107. '#type' => 'select',
  108. '#title' => t('Text source'),
  109. '#default_value' => $data['text_source'],
  110. '#options' => array(
  111. 'alt' => t('Image alt'),
  112. 'title' => t('Image title'),
  113. 'text' => t('Text (with token replacement)'),
  114. 'php' => t('PHP code'),
  115. ),
  116. ),
  117. 'text' => array(
  118. '#type' => 'textfield',
  119. '#title' => t('Text'),
  120. '#default_value' => $data['text'],
  121. '#states' => array(
  122. 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'text')),
  123. ),
  124. ),
  125. 'token_help' => array(
  126. '#type' => 'fieldset',
  127. '#title' => t('Token replacement patterns'),
  128. '#collapsible' => TRUE,
  129. '#collapsed' => TRUE,
  130. 'tree' => array(
  131. '#theme' => 'token_tree',
  132. '#token_types' => $tokens,
  133. ),
  134. 'token_module_notice' => array(
  135. '#markup' => !module_exists('token') ? t('You might want to enable the token module to view token replacement patterns.') : '',
  136. ),
  137. 'entity_token_module_notice' => array(
  138. '#markup' => !module_exists('entity_token') ? t('You might want to enable the entity_token module to get token options for all entities and field properties.') : '',
  139. ),
  140. '#states' => array(
  141. 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'text')),
  142. ),
  143. ),
  144. 'php' => array(
  145. '#type' => 'textarea',
  146. '#rows' => 12,
  147. '#title' => t('PHP code'),
  148. '#default_value' => $data['php'],
  149. '#disabled' => !$allow_php,
  150. '#states' => array(
  151. 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'php')),
  152. ),
  153. '#wysiwyg' => FALSE,
  154. ),
  155. 'text_case' => array(
  156. '#title' => t('Capitalization'),
  157. '#type' => 'select',
  158. '#options' => array(
  159. 'none' => t('No transform'),
  160. 'upper' => t('Upper case'),
  161. 'lower' => t('Lower case'),
  162. 'ucfirst' => t('Capitalize first letter'),
  163. 'ucwords' => t('Capitalize each word'),
  164. ),
  165. '#default_value' => $data['text_case'],
  166. '#description' => t('Defines if and how to transform the case of the text to render.'),
  167. ),
  168. 'fontfile' => array(
  169. '#type' => 'textfield',
  170. '#title' => t('Font file name'),
  171. '#default_value' => $data['fontfile'],
  172. '#description' => imagecache_actions_file_field_description(),
  173. '#element_validate' => array('image_effects_text_validate_font'),
  174. '#size' => 80,
  175. ),
  176. 'size' => array(
  177. '#type' => 'textfield',
  178. '#title' => t('Font size'),
  179. '#default_value' => $data['size'],
  180. '#description' => t('The font size in points. Only in GD1 this is in pixels.'),
  181. '#size' => 3,
  182. ),
  183. 'RGB' => imagecache_rgb_form($data['RGB']),
  184. 'alpha' => array(
  185. '#type' => 'textfield',
  186. '#title' => t('Opacity'),
  187. '#default_value' => $data['alpha'],
  188. '#size' => 3,
  189. '#description' => t('Opacity: 1-100.'),
  190. ),
  191. 'xpos' => array(
  192. '#type' => 'textfield',
  193. '#title' => t('X offset'),
  194. '#default_value' => $data['xpos'],
  195. '#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.'),
  196. '#size' => 10,
  197. ),
  198. 'ypos' => array(
  199. '#type' => 'textfield',
  200. '#title' => t('Y offset'),
  201. '#default_value' => $data['ypos'],
  202. '#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.'),
  203. '#size' => 10,
  204. ),
  205. 'halign' => array(
  206. '#type' => 'select',
  207. '#title' => t('Horizontal alignment'),
  208. '#default_value' => $data['halign'],
  209. '#description' => t('The horizontal alignment of the text around the given %xpos.', array('%xpos' => t('X offset'))),
  210. '#options' => array(
  211. 'left' => t('Left'),
  212. 'center' => t('Center'),
  213. 'right' => t('Right')
  214. ),
  215. ),
  216. 'valign' => array(
  217. '#type' => 'select',
  218. '#title' => t('Vertical alignment'),
  219. '#default_value' => $data['valign'],
  220. '#description' => t('The vertical alignment of the text around the given %ypos.', array('%ypos' => t('Y offset'))),
  221. '#options' => array(
  222. 'top' => t('Top'),
  223. 'center' => t('Center'),
  224. 'bottom' => t('Bottom')
  225. ),
  226. ),
  227. 'angle' => array(
  228. '#type' => 'textfield',
  229. '#title' => t('Angle'),
  230. '#default_value' => $data['angle'],
  231. '#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.'),
  232. '#size' => 3,
  233. ),
  234. );
  235. if (!$allow_php && $data['text_source'] !== 'php') {
  236. unset($form['text_fieldset']['text_source']['#options']['php']);
  237. }
  238. $form['#element_validate'][] = 'image_effects_text_form_validate';
  239. return $form;
  240. }
  241. /**
  242. * Element validation callback for the effect form.
  243. * @see http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#element_validate
  244. */
  245. function image_effects_text_form_validate(array $element/*, &$form_state, $form*/) {
  246. if (!is_numeric($element['size']['#value']) || $element['size']['#value'] <= 0) {
  247. form_error($element['size'], t('%field must be a positive number.', array('%field' => t('Font size'))));
  248. }
  249. if (!is_numeric($element['alpha']['#value']) || $element['alpha']['#value'] < 0 || $element['alpha']['#value'] > 100) {
  250. form_error($element['alpha'], t('%field must be a number between 1 and 100.', array('%field' => t('Opacity'))));
  251. }
  252. if (!is_numeric($element['angle']['#value'])) {
  253. form_error($element['angle'], t('%field must be a number.', array('%field' => t('Angle'))));
  254. }
  255. }
  256. /**
  257. * Validates that the file as specified in the element exists and is readable.
  258. *
  259. * This is a Form API #element_validate callback.
  260. *
  261. * @param array $element
  262. */
  263. function image_effects_text_validate_font(array &$element/*, &$form_status*/) {
  264. if (!imagecache_actions_find_file($element['#value'])) {
  265. drupal_set_message(t("Unable to find the font file '%file'. Please check the path. You can ignore this waning, if the font refers to a system or toolkit font, and the text shows correctly.", array('%file' => $element['#value'])), 'warning');
  266. }
  267. }
  268. /**
  269. * Implements theme_hook() for the text effect summary.
  270. *
  271. * @param array $variables
  272. * An associative array containing:
  273. * - data: The current configuration for this image effect.
  274. *
  275. * @return string
  276. * The HTML for the summary of this image effect.
  277. * @ingroup themeable
  278. */
  279. function theme_image_effects_text_summary(array $variables) {
  280. $data = $variables['data'];
  281. switch ($data['text_source']) {
  282. case 'alt':
  283. $text = 'image alt';
  284. break;
  285. case 'title':
  286. $text = 'image title';
  287. break;
  288. case 'text':
  289. $text = $data['text'];
  290. break;
  291. case 'php':
  292. $text = 'PHP code';
  293. break;
  294. default:
  295. $text = '';
  296. break;
  297. }
  298. return 'Text: ' . $text . '; Position: ' . $data['xpos'] . ',' . $data['ypos'] . '; Alignment: ' . $data['halign'] . ',' . $data['valign'];
  299. }
  300. /**
  301. * Image effect callback for the text effect.
  302. *
  303. * @param stdClass $image
  304. * @param array $data
  305. *
  306. * @return boolean
  307. * true on success, false otherwise.
  308. */
  309. function image_effects_text_effect_inc(stdClass $image, array $data) {
  310. // Use of imagecache_actions_hex2rgba() ,the imagecache_file_...() functions,
  311. // and imagecache_actions_get_image_context() create a dependency on
  312. // file utility.inc.
  313. module_load_include('inc', 'imagecache_actions', 'utility');
  314. // Massage the data and pass it on to the toolkit dependent part.
  315. // Start with a straight copy.
  316. $params = $data;
  317. // Get the text to overlay.
  318. $params['text'] = image_effects_text_get_text($image, $params);
  319. if (empty($params['text'])) {
  320. // No text to overlay. This is a situation that can be expected with token
  321. // replacement, or custom PHP code. Return immediately with success.
  322. return TRUE;
  323. }
  324. // Find out where the font file is located and if it is readable.
  325. $params['fontpath'] = imagecache_actions_find_file($data['fontfile']);
  326. if ($params['fontpath'] === FALSE) {
  327. // Pass the font on and let's hope that the toolkit knows where to find it.
  328. // We did warn on the effect form.
  329. $params['fontpath'] = $data['fontfile'];
  330. }
  331. // Parse offsets.
  332. $params['xpos'] = image_effects_text_get_offset($data['xpos'], $image->info['width'], $image->info['height'], $image->info['width']);
  333. $params['ypos'] = image_effects_text_get_offset($data['ypos'], $image->info['width'], $image->info['height'], $image->info['height']);
  334. // Convert color from hex (as it is stored in the UI).
  335. $params['RGB'] = $data['RGB'];
  336. if ($params['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($params['RGB']['HEX'])) {
  337. $params['RGB'] += $deduced;
  338. }
  339. // Make integers of various parameters.
  340. $params['size'] = (int) $params['size'];
  341. $params['xpos'] = (int) $params['xpos'];
  342. $params['ypos'] = (int) $params['ypos'];
  343. // Hand over to toolkit.
  344. return image_toolkit_invoke('image_effects_text', $image, array($params));
  345. }
  346. /**
  347. * GD toolkit specific implementation of the text effect.
  348. *
  349. * @param stdClass $image
  350. * @param array $data
  351. * The parameters for this effect.
  352. *
  353. * @return bool
  354. * true on success, false otherwise.
  355. */
  356. function image_gd_image_effects_text(stdClass $image, array $data) {
  357. // Convert color and alpha to GD alpha and color value.
  358. // GD alpha value: 0 = opaque, 127 = transparent.
  359. $data['alpha'] = (int) ((1 - ($data['alpha'] / 100)) * 127);
  360. $color = imagecolorallocatealpha($image->resource, $data['RGB']['red'], $data['RGB']['green'], $data['RGB']['blue'], $data['alpha']);
  361. if ($color !== FALSE) {
  362. $bounds = NULL;
  363. // Adjust Y position for vertical alignment (if different from bottom).
  364. if ($data['valign'] !== 'bottom') {
  365. // Get bounding box.
  366. // PHP Manual: "This function requires both the GD library and the » FreeType library."
  367. // So it is not more demanding than imagettftext, which we need anyway.
  368. $bounds = imagettfbbox($data['size'], 0, $data['fontpath'], $data['text']);
  369. if (!$bounds) {
  370. drupal_set_message(t('Failed to calculate text dimensions using GD toolkit. Ignoring the alignment settings.'), 'warning');
  371. }
  372. else {
  373. // Get height of bounding box.
  374. $height = $bounds[1] - $bounds[7];
  375. // Shift ypos down (full height on bottom, half the height on center).
  376. $data['ypos'] += $data['valign'] === 'center' ? (int) ($height / 2) : $height;
  377. }
  378. }
  379. // Adjust X position for horizontal alignment (if different from left).
  380. if ($data['halign'] !== 'left') {
  381. // Get bounding box. PHP Manual: "This function requires both the GD
  382. // library and the » FreeType library.", so it is not more demanding than
  383. // imagettftext(), which we need anyway.
  384. if ($bounds === NULL) {
  385. $bounds = imagettfbbox($data['size'], 0, $data['fontpath'], $data['text']);
  386. if (!$bounds) {
  387. drupal_set_message(t('Failed to calculate text dimensions using GD toolkit. Ignoring the alignment.'), 'warning');
  388. }
  389. }
  390. if ($bounds !== FALSE) {
  391. // Get width of bounding box.
  392. $width = $bounds[2] - $bounds[0];
  393. // Shift xpos to the left (full width on right, half the width on center).
  394. $data['xpos'] -= $data['halign'] === 'center' ? (int) ($width / 2) : $width;
  395. }
  396. }
  397. // PHP Manual: "This function requires both the GD library and the » FreeType library."
  398. $bounds = imagettftext($image->resource, $data['size'], $data['angle'], $data['xpos'], $data['ypos'], $color, $data['fontpath'], $data['text']);
  399. return $bounds !== FALSE;
  400. }
  401. return FALSE;
  402. }
  403. /**
  404. * Imagemagick toolkit specific implementation of the text effect.
  405. *
  406. * Text in Imagemagick:
  407. * - http://www.imagemagick.org/script/command-line-options.php?#draw
  408. * - http://www.imagemagick.org/script/command-line-options.php?#annotate
  409. *
  410. * UTF-8/non-ascii characters:
  411. * To prevent problems with non-ASCII characters, the online manual suggests to
  412. * put the text in a file and use the @{file} syntax. This does not work with
  413. * the text primitive of the -draw command, so we use -annotate.
  414. * We put the text in a temporary file which will be deleted by our hook_exit().
  415. * http://www.imagemagick.org/Usage/windows/#character_encoding
  416. *
  417. * Alignment in Imagemagick:
  418. * This is not directly supported, though a justification option has been
  419. * proposed: http://www.imagemagick.org/Usage/bugs/future/#justification.
  420. *
  421. * What we do have is the gravity option:
  422. * Gravity is used to position a text, but it also automatically applies a
  423. * justification based on that placement. So we use gravity here for alignment,
  424. * but will thus have to rebase our positioning.
  425. * - http://www.imagemagick.org/Usage/annotating/#gravity
  426. *
  427. * Gravity |halign|valign |hpos change|vpos change
  428. * ------------------------------------------------
  429. * NorthWest left top 0 0
  430. * North center top -width/2 0
  431. * NorthEast right top -width 0
  432. * West left center 0 -height/2
  433. * Center center center -width/2 -height/2
  434. * East right center -width -height/2
  435. * SouthWest left bottom 0 -height
  436. * South center bottom -width/2 -height
  437. * SouthEast right bottom -width -height
  438. *
  439. * @param stdClass $image
  440. * @param array $data
  441. * The parameters for this effect.
  442. *
  443. * @return bool
  444. * true on success, false otherwise.
  445. */
  446. function image_imagemagick_image_effects_text(stdClass $image, array $data) {
  447. static $alignments2gravity = array(
  448. 'left' => array(
  449. 'top' => array(
  450. 'gravity' => 'NorthWest',
  451. 'tx' => 0,
  452. 'ty' => 0,
  453. ),
  454. 'center' => array(
  455. 'gravity' => 'West',
  456. 'tx' => 0,
  457. 'ty' => -0.5,
  458. ),
  459. 'bottom' => array(
  460. 'gravity' => 'SouthWest',
  461. 'tx' => 0,
  462. 'ty' => 1, // reversed translation
  463. ),
  464. ),
  465. 'center' => array(
  466. 'top' => array(
  467. 'gravity' => 'North',
  468. 'tx' => -0.5,
  469. 'ty' => 0,
  470. ),
  471. 'center' => array(
  472. 'gravity' => 'Center',
  473. 'tx' => -0.5,
  474. 'ty' => -0.5,
  475. ),
  476. 'bottom' => array(
  477. 'gravity' => 'South',
  478. 'tx' => -0.5,
  479. 'ty' => 1, // reversed translation
  480. ),
  481. ),
  482. 'right' => array(
  483. 'top' => array(
  484. 'gravity' => 'NorthEast',
  485. 'tx' => 1, // reversed translation
  486. 'ty' => 0,
  487. ),
  488. 'center' => array(
  489. 'gravity' => 'East',
  490. 'tx' => 1, // reversed translation
  491. 'ty' => -0.5,
  492. ),
  493. 'bottom' => array(
  494. 'gravity' => 'SouthEast',
  495. 'tx' => 1, // reversed translation
  496. 'ty' => 1, // reversed translation
  497. ),
  498. ),
  499. );
  500. // Convert color and alpha to Imagemagick rgba color argument.
  501. $alpha = $data['alpha'] / 100;
  502. $color = 'rgba(' . $data['RGB']['red'] . ',' . $data['RGB']['green'] . ',' . $data['RGB']['blue'] . ',' . $alpha . ')';
  503. // Set gravity for the alignment and calculate the translation to the
  504. // requested x and y offset as starting from the gravity point.
  505. $alignment_corrections = $alignments2gravity[$data['halign']][$data['valign']];
  506. $gravity = $alignment_corrections['gravity'];
  507. if ($alignment_corrections['tx'] > 0) {
  508. $data['xpos'] = (int) ($alignment_corrections['tx'] * $image->info['width'] - $data['xpos']);
  509. }
  510. else {
  511. $data['xpos'] += (int) ($alignment_corrections['tx'] * $image->info['width']);
  512. }
  513. if ($alignment_corrections['ty'] > 0) {
  514. $data['ypos'] = (int) ($alignment_corrections['ty'] * $image->info['height'] - $data['ypos']);
  515. }
  516. else {
  517. $data['ypos'] += (int) ($alignment_corrections['ty'] * $image->info['height']);
  518. }
  519. // Add signs to translation, also when positive or 0.
  520. if ($data['xpos'] >= 0) {
  521. $data['xpos'] = '+' . $data['xpos'];
  522. }
  523. if ($data['ypos'] >= 0) {
  524. $data['ypos'] = '+' . $data['ypos'];
  525. }
  526. // Angle must be positive.
  527. if ($data['angle'] < 0) {
  528. $data['angle'] = $data['angle'] % 360 + 360;
  529. }
  530. // Set font file, size and color. fontpath is the real path, not a wrapper.
  531. $image->ops[] = '-font ' . escapeshellarg($data['fontpath']);
  532. $image->ops[] = "-pointsize {$data['size']}";
  533. $image->ops[] = '-fill ' . escapeshellarg($color);
  534. // Add text to a temporary file,
  535. $tmp_file_name = drupal_tempnam('temporary://', 'image_effects_text');
  536. $tmp_file = fopen($tmp_file_name, 'w');
  537. if ($tmp_file) {
  538. fwrite($tmp_file, $data['text']);
  539. fclose($tmp_file);
  540. // and inform our hook_exit about it.
  541. image_effects_text_exit($tmp_file_name);
  542. $tmp_file_name = drupal_realpath($tmp_file_name);
  543. $text = "@$tmp_file_name";
  544. }
  545. else {
  546. // Fallback to pass the text via the command line, let's hope there are no
  547. // non-ASCII characters or that it works anyway (OS and locale dependent).
  548. $text = $data['text'];
  549. }
  550. // Add gravity.
  551. $image->ops[] = "-gravity $gravity";
  552. // Add text angle, position and text (file) itself.
  553. $image->ops[] = "-annotate {$data['angle']}x{$data['angle']}{$data['xpos']}{$data['ypos']} " . escapeshellarg($text);
  554. return TRUE;
  555. }
  556. /**
  557. * UTILITY
  558. */
  559. /**
  560. * Convert a position into an offset in pixels.
  561. *
  562. * Position may be a number of additions and/or subtractions of:
  563. * - An value, positive or negative, in pixels.
  564. * - A, positive or negative, percentage (%). The given percentage of the
  565. * current dimension will be taken.
  566. * - 1 of the keywords:
  567. * * top: 0
  568. * * bottom: the height of the current image
  569. * * left: 0
  570. * * right: the width of the current image
  571. * * center: 50% (of the current dimension)
  572. * Examples:
  573. * 0, 20, -20, 90%, 33.3% + 10, right, center - 20, 300 - center, bottom - 50.
  574. * Note:
  575. * The algorithm will accept many more situations, though the result may be hard
  576. * to predict.
  577. *
  578. * @param string $position
  579. * The string defining the position.
  580. * @param int $width
  581. * The length of the horizontal dimension.
  582. * @param int $height
  583. * The length of the vertical dimension.
  584. * @param int $length
  585. * The length of the current dimension (should be either width or height).
  586. *
  587. * @return number
  588. * The computed offset in pixels.
  589. */
  590. function image_effects_text_get_offset($position, $width, $height, $length) {
  591. $value = 0;
  592. $tokens = preg_split('/ *(-|\+) */', $position, 0, PREG_SPLIT_DELIM_CAPTURE);
  593. $sign = 1;
  594. foreach ($tokens as $token) {
  595. switch ($token) {
  596. case '+';
  597. // Ignore, doesn't change the sign
  598. break;
  599. case '-';
  600. // Flip the sign.
  601. $sign = -$sign;
  602. break;
  603. case 'top':
  604. case 'left':
  605. // Actually, top and left are a no-op.
  606. $value += $sign * 0;
  607. $sign = 1;
  608. break;
  609. case 'bottom':
  610. // Use height of the image, even if this is for the horizontal position.
  611. $value += $sign * $height;
  612. $sign = 1;
  613. break;
  614. case 'right':
  615. // Use width of the image, even if this is for the vertical position.
  616. $value += $sign * $width;
  617. $sign = 1;
  618. break;
  619. case 'center':
  620. // half the current dimension as provided by $length.
  621. $value += $sign * $length / 2;
  622. $sign = 1;
  623. break;
  624. default:
  625. // Value: absolute or percentage
  626. if (substr($token, -strlen('%')) === '%') {
  627. $percentage = ((float) substr($token, 0, -strlen('%'))) / 100.0;
  628. $value += $sign * ($percentage * $length);
  629. }
  630. else {
  631. $value += $sign * (float) $token;
  632. }
  633. $sign = 1;
  634. break;
  635. }
  636. }
  637. return $value;
  638. }
  639. /**
  640. * Get the text to use for this image.
  641. *
  642. * @param stdClass $image
  643. * The image the current effect is to be applied to.
  644. * @param array $data
  645. * An array containing the effect data.
  646. *
  647. * @return string
  648. * Plain text to be placed on the image.
  649. */
  650. function image_effects_text_get_text(stdClass $image, array $data) {
  651. // Get context about the image.
  652. $image_context = imagecache_actions_get_image_context($image, $data);
  653. if ($data['text_source'] === 'text') {
  654. // Replace \n with a newline character, except when preceded by a \.
  655. $text = preg_replace('/([^\\\\])\\\\n/', "$1\n", $data['text']);
  656. // Replace \\n by \n.
  657. $text = preg_replace('/\\\\\\\\n/', '\n', $text);
  658. // Replace tokens.
  659. $token_data = array();
  660. foreach ($image_context['referring_entities'] as /*$field_name =>*/ $field_referring_entities) {
  661. foreach ($field_referring_entities as $entity_type => $entities) {
  662. // We can pass only 1 entity per given type to token_replace(), we take
  663. // the first.
  664. $token_data[$entity_type] = reset($entities);
  665. }
  666. }
  667. if ($image_context['managed_file']) {
  668. $token_data['file'] = $image_context['managed_file'];
  669. }
  670. // We should not sanitize the text as it will not be rendered in the browser
  671. // but is rendered on the image canvas on the server.
  672. $text = token_replace($text, $token_data, array('clear' => TRUE, 'sanitize' => FALSE));
  673. }
  674. else if ($data['text_source'] === 'alt' || $data['text_source'] === 'title') {
  675. $text = '';
  676. // We have 2 possible sources for the alt or title text:
  677. // - Image field.
  678. // - Media module (7.x-2.x) with file_entity: the alt and title come as
  679. // fields of the file entity, stored in 'managed_file'. The names of the
  680. // fields are field_file_image_alt_text resp. field_file_image_title_text.
  681. // BTW: these fields are also available in the 'image_field' entry, but as
  682. // a managed file may be existing without any image field referring to it,
  683. // we do the lookup in the managed_file entry.
  684. if (!empty($image_context['image_field'][$data['text_source']])) {
  685. $text = $image_context['image_field'][$data['text_source']];
  686. }
  687. else if (!empty($image_context['managed_file'])) {
  688. $field = field_get_items('file', $image_context['managed_file'], "field_file_image_{$data['text_source']}_text");
  689. if ($field) {
  690. $text = $field[0]['value'];
  691. }
  692. }
  693. }
  694. else { // $data['text_source'] === 'php'
  695. // Process the php using php_eval (rather than eval), but with GLOBAL
  696. // variables, so they can be passed successfully.
  697. $GLOBALS['image_context'] = $image_context;
  698. $GLOBALS['image'] = $image;
  699. // Get (non-alterable) context about the image style and image effect.
  700. $execution_info = imagecache_actions_get_image_effect_context();
  701. $GLOBALS['image_style'] = $execution_info['image_style'];
  702. $GLOBALS['image_effect_id'] = $execution_info['image_effect_id'];
  703. // We don't need to check_plain() the resulting text, as the text is not
  704. // rendered in a browser but processed on the server.
  705. $text = module_exists('php') ? php_eval('<' . '?php global $image, $image_context; ' . $data['php'] . ' ?' . '>') : '';
  706. unset($GLOBALS['image_effect_id']);
  707. unset($GLOBALS['image_style']);
  708. unset($GLOBALS['image']);
  709. unset($GLOBALS['image_context']);
  710. }
  711. // Convert case.
  712. $text = image_effect_text_case_transform($text, isset($data['text_case']) ? $data['text_case'] : 'none');
  713. return $text;
  714. }
  715. /**
  716. * Transform a string by a certain method.
  717. *
  718. * Proudly copied from module://views/includes/handlers.inc.
  719. *
  720. * @param $string
  721. * The input you want to transform.
  722. * @param $option
  723. * How do you want to transform it, possible values:
  724. * - upper: Uppercase the string.
  725. * - lower: lowercase the string.
  726. * - ucfirst: Make the first char uppercase.
  727. * - ucwords: Make each word in the string uppercase.
  728. *
  729. * @return string
  730. * The transformed string.
  731. */
  732. function image_effect_text_case_transform($string, $option) {
  733. global $multibyte;
  734. switch ($option) {
  735. default:
  736. return $string;
  737. case 'upper':
  738. return drupal_strtoupper($string);
  739. case 'lower':
  740. return drupal_strtolower($string);
  741. case 'ucfirst':
  742. return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
  743. case 'ucwords':
  744. if ($multibyte == UNICODE_MULTIBYTE) {
  745. return mb_convert_case($string, MB_CASE_TITLE);
  746. }
  747. else {
  748. return ucwords($string);
  749. }
  750. }
  751. }