Полезняшки. Встраивание генерируемых "на лету" изображений деревьев в веб-страницы

November 19, 2007

Выложены исходники небольшого решения на javascript+php. Его назначение - генерация изображения дерева согласно текстовому описанию в html-коде страницы.

Основная цель данного решения - упрощение создания документации, содержащей изображения деревьев.

Если вы хотите разместить в коде страницы дерево некоторой иерархии (например, файловой системы),

то не нужно делать скриншот окна проводника, сохранять его как файл картинки на сервере. При частом изменении (переработке документации) эти шаги могут занимать длительное время и почему бы их не упростить и автоматизировать?

Давайте попробуем. Прежде всего, вы подключаете к своей веб-странице js-файлы
  1. jquery.js - известная библиотека jquery (http://jquery.com)
  2. make_live_tree.js - собственно, javascript-часть моей библиотечки

Затем в тексте страницы, там где вы хотите вставить изображение файлового дерева, пишите следующий код:
  1. <div class="treetag"> 
  2.   -food
  3.    -fruits
  4.      apple
  5.    +orange
  6.    -milk
  7.      fresh
  8.      old
  9.   -manufacture
  10.     -axe
  11.      +stone
  12.     +screwdriver
  13.  </div>
Внутрь тега div, помеченного классом treetag, вы помещаете описание элементов дерева. Очевидно, что уровень узла задается его отступом, а иконка для узла задается знаком стоящим перед названием узла.
  • Так знак "+" обозначает, что данный узел еще не был "раскрыт".
  • Знак "-" говорит, что узел был "раскрыт".
  • Пробел говорит, что данный узел является конечным.

Опционально можно задать для тега div атрибуты spec_w и spec_h - размер генерируемой картинки, если вы это не сделаете, то размер изображения будет вычислен автоматически.

Когда страница загружается, javascript-часть находит все теги помеченные классом treetag и отправляет запрос серверной части скрипта на генерацию изображения, которое и вставляется внутрь тега div, вместо его старого текстового содержимого.

Созданные изображения кэшируются, чтобы уменьшить нагрузку на сервер, изображения помещаются в папку tmp, находящуюся там же где и php-часть скрипта.

Пример созданного изображения приведен ниже:



Код javascript-части make_live_tree.js


  1. var URL_FOR_CHART_GEN = 'makechartget.php';
  2.      $(document).ready(function(){
  3. 	$.each( $('div.treetag'), function(i, n){
  4.           n = $(n);
  5. 	  var spec_w = n.attr ('spec_w');
  6. 	  var spec_h = n.attr ('spec_h');
  7. 	  n.load(URL_FOR_CHART_GEN + '?maketag=true',
  8. 	   {spec_w: spec_w, spec_h: spec_h, content: n.html()},
  9. 		   function() {} 
  10. 	  );	
  11. 	});
  12.      });

Код php-части makechartget.php


  1. <?php
  2. function get_spaces_count ($s){
  3. 	return strlen($s) - strlen(ltrim($s));
  4. }
  5.  
  6. function get_adapted_text_part ($s){
  7. 	for ($i = 0; $i < strlen($s); $i++){
  8. 		if ($s[$i] == '-' || $s[$i] == '+')
  9. 		return substr($s , $i);
  10. 		if ($s [$i] != ' ')
  11. 		return substr($s, $i);
  12. 	}
  13. 	return '';
  14. }
  15.  
  16. $spaces_8 = '';
  17. for ($i = 0; $i < 8; $i++)
  18. $spaces_8 = $spaces_8 + ' ';
  19.  
  20. $font_file = 'arial.ttf';
  21. $font_angle = 0;
  22. $font_size = 14;
  23.  
  24. $maketag = false;
  25. if (isset($_REQUEST['maketag'])){
  26. 	$maketag = $_REQUEST['maketag'] == 'true';
  27. }
  28.  
  29. $loadimg = false;
  30. if (isset($_REQUEST['loadimg'])){
  31. 	$loadimg = $_REQUEST['loadimg'] == 'true';
  32. }
  33.  
  34. if ($loadimg){
  35. 	$guid = $_REQUEST['guid'];
  36. 	$fname = dirname(__FILE__). '/tmp/' . $guid;
  37.  
  38. 	$loadimg = $_REQUEST['loadimg'] == 'true';
  39.  
  40. 	$im = imagecreatefrompng ($fname);
  41. 	imagepng($im);
  42. 	imagedestroy($im);
  43. 	die ();
  44. }
  45.  
  46. if ($maketag){
  47. 	// drop all tmp pics ----
  48. 	if (0){
  49. 		$dir = dirname(__FILE__) . '/tmp/';
  50. 		$dh = opendir($dir);
  51. 		while (($file = readdir($dh))){
  52. 			if ($file == '.' || $file == '..') continue;
  53. 			if (is_file($dir . $file))
  54. 			unlink($dir . $file);
  55. 		}
  56. 		closedir($dh);
  57. 	}
  58. 	// ----------------------
  59.  
  60. 	$spec_w = $_REQUEST['spec_w'];
  61. 	$spec_h = $_REQUEST['spec_h'];
  62.  
  63. 	if (! is_numeric($spec_w)){
  64. 		$spec_w = -1;
  65. 	}
  66. 	if (! is_numeric($spec_h)){
  67. 		$spec_h = -1;
  68. 	}
  69.  
  70. 	$content_orig = $_REQUEST['content'];
  71. 	$content = explode("\n", $content_orig);
  72.  
  73. 	$max_font_size_w = 0;
  74. 	$max_font_size_h = 0;
  75. 	$sum_font_size_h = 0;
  76. 	$max_spaces_count = 0;
  77.  
  78. 	$matrix = array ();
  79.  
  80. 	for ($i = 0; $i < count($content); $i++){
  81. 		if (trim($content[$i]) == '') continue;
  82. 		$tmp = str_replace("\t" , $spaces_8, rtrim($content[$i]));
  83. 		$tmp_text = get_adapted_text_part ($tmp);
  84.  
  85. 		$cmb = ' ';
  86. 		$clean_text = '';
  87. 		$count_spaces = get_spaces_count($tmp);
  88. 		if ($tmp_text [0] == '+'){
  89. 			$cmb = '+';
  90. 			$clean_text = substr($tmp_text, 1);
  91. 		}
  92. 		elseif ($tmp_text [0] == '-'){
  93. 			$cmb = '-';
  94. 			$clean_text = substr($tmp_text, 1);
  95. 		}
  96. 		else{
  97. 			$cmb = ' ';
  98. 			$clean_text = $tmp_text;
  99. 			$count_spaces--;
  100. 		}
  101.  
  102. 		$font_info =  imagettfbbox($font_size, $font_angle, $font_file, $clean_text);
  103. 		$maxi = max ($font_info);
  104. 		for ($k = 0; $k < count($font_info); $k++)
  105. 		$font_info [$k] += $maxi;
  106. 		$nova_w = abs($font_info [4] - $font_info [0]);
  107. 		$nova_h = abs($font_info [5] - $font_info [1]);
  108. 		$max_font_size_w = max ($max_font_size_w, $nova_w);
  109. 		$max_font_size_h = max ($max_font_size_h, $nova_h);
  110. 		$sum_font_size_h += $nova_h;
  111. 		$max_spaces_count = max ($max_spaces_count, $count_spaces);
  112.  
  113. 		$matrix [] = array ('clean_text' => $clean_text, 'symbol' => $cmb, 
  114. 'count_spaces' => $count_spaces, 'text_w' => $nova_w, 'text_h' => $nova_h);
  115. 	}
  116.  
  117. 	$pref_spec_w = 20 + $max_font_size_w + $max_spaces_count * 20 + 40;
  118. 	$pref_spec_h = 20 + $max_font_size_h * count($matrix);
  119.  
  120. 	if ($spec_w == -1){
  121. 		$spec_w = $pref_spec_w;
  122. 	}
  123. 	if ($spec_h == -1){
  124. 		$spec_h = $pref_spec_h;
  125. 	}
  126.  
  127. 	$guid = md5($content_orig) . '_' . $spec_w . '_' . $spec_h;
  128. 	$fname = dirname(__FILE__). '/tmp/' . $guid;
  129. 	if (file_exists($fname)){
  130. 		die ('<img src="makechartget.php?loadimg=true&guid='.$guid.
  131. '" width="'.$spec_w.'" height="'.$spec_h.'" alt="" border="0" />');
  132. 	}
  133.  
  134. 	$koeff_w = $spec_w / $pref_spec_w;
  135. 	$koeff_h = $spec_h / $pref_spec_h;
  136.  
  137. 	$im = imagecreatetruecolor($pref_spec_w, $pref_spec_h);
  138. 	$COLOR_WHITE = imagecolorallocate($im , 255, 255, 255);
  139. 	$COLOR_BLACK = imagecolorallocate($im , 0, 0, 0);
  140.  
  141. 	$IMAGE_PLUS = imagecreatefrompng(dirname(__FILE__) . '/img_folder_plus.png');
  142. 	$size_w_image_folder_plus = imagesx($IMAGE_PLUS);
  143. 	$size_h_image_folder_plus = imagesy($IMAGE_PLUS);
  144.  
  145. 	$IMAGE_MINUS = imagecreatefrompng(dirname(__FILE__) . '/img_folder_minus.png');
  146. 	$size_w_image_folder_minus = imagesx($IMAGE_MINUS);
  147. 	$size_h_image_folder_minus = imagesy($IMAGE_MINUS);
  148.  
  149. 	$IMAGE_NONE = imagecreatefrompng(dirname(__FILE__) . '/img_folder_none.png');
  150. 	$size_w_image_folder_none = imagesx($IMAGE_NONE);
  151. 	$size_h_image_folder_none = imagesy($IMAGE_NONE);
  152.  
  153. 	imagefilledrectangle ($im, 0, 0, $pref_spec_w, $pref_spec_h, $COLOR_WHITE);
  154.  
  155. 	for ($i = 0; $i < count($matrix); $i++){
  156. 		if ($matrix[$i]['symbol'] == '+'){
  157. 			imagecopy($im , $IMAGE_PLUS, 20 * $matrix[$i]['count_spaces'] + 10, 0 +
  158.  $max_font_size_h*$i, 0, 0, $size_w_image_folder_plus, $size_h_image_folder_plus);
  159. 		}
  160. 		if ($matrix[$i]['symbol'] == '-'){
  161. 			imagecopy($im , $IMAGE_MINUS, 20 * $matrix[$i]['count_spaces'] + 10, 0 +
  162.  $max_font_size_h*$i, 0, 0, $size_w_image_folder_minus, $size_h_image_folder_minus);			
  163. 		}
  164. 		if ($matrix[$i]['symbol'] == ' '){
  165. 			imagecopy($im , $IMAGE_NONE, 20 * $matrix[$i]['count_spaces'] + 10, 0 + 
  166. $max_font_size_h*$i, 0, 0, $size_w_image_folder_none, $size_h_image_folder_none);						
  167. 		}
  168.  
  169. 		imagettftext($im, $font_size, $font_angle, 20 * $matrix[$i]['count_spaces'] + 
  170. 40 + 10, 15 + $max_font_size_h*$i, $COLOR_BLACK, $font_file, $matrix[$i]['clean_text']);
  171. 	}
  172.  
  173. 	imagedestroy($IMAGE_PLUS);
  174. 	imagedestroy($IMAGE_MINUS);
  175. 	imagedestroy($IMAGE_NONE);
  176. 	imagecolordeallocate ($im , $COLOR_BLACK);
  177. 	imagecolordeallocate ($im , $COLOR_WHITE);
  178.  
  179.  
  180. 	if ($pref_spec_h != $spec_h && $pref_spec_w != $spec_w){
  181. 		// Resample
  182. 		$image_p = imagecreatetruecolor($spec_w, $spec_h);
  183. 		imagecopyresampled($image_p, $im, 0, 0, 0, 0, 
  184. $spec_w, $spec_h, $pref_spec_w, $pref_spec_h);
  185. 		imagepng ($image_p , $fname);
  186. 		imagedestroy($image_p);
  187. 		imagedestroy($im);
  188. 	}
  189. 	else{
  190. 		imagepng ($im , $fname);
  191. 		imagedestroy($im);
  192.  
  193. 	}
  194.  
  195. 	die ('<img src="makechartget.php?loadimg=true&guid='.$guid.'" width="'.$spec_w.
  196. '" height="'.$spec_h.'" alt="" border="0" />');
  197. }
  198. ?>