SvgIconBuilder.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace Drupal\Core\Layout\Icon;
  3. use Drupal\Component\Utility\Html;
  4. /**
  5. * Builds SVG layout icons.
  6. */
  7. class SvgIconBuilder implements IconBuilderInterface {
  8. /**
  9. * The machine name of the layout.
  10. *
  11. * @var string
  12. */
  13. protected $id;
  14. /**
  15. * The label of the layout.
  16. *
  17. * @var string
  18. */
  19. protected $label;
  20. /**
  21. * The width of the SVG.
  22. *
  23. * @var int
  24. */
  25. protected $width = 125;
  26. /**
  27. * The height of the SVG.
  28. *
  29. * @var int
  30. */
  31. protected $height = 150;
  32. /**
  33. * The padding between regions.
  34. *
  35. * @var int
  36. */
  37. protected $padding = 4;
  38. /**
  39. * The width of region borders.
  40. *
  41. * @var int|null
  42. */
  43. protected $strokeWidth = 1;
  44. /**
  45. * {@inheritdoc}
  46. */
  47. public function build(array $icon_map) {
  48. $regions = $this->calculateSvgValues($icon_map, $this->width, $this->height, $this->strokeWidth, $this->padding);
  49. return $this->buildRenderArray($regions, $this->width, $this->height, $this->strokeWidth);
  50. }
  51. /**
  52. * Builds a render array representation of an SVG.
  53. *
  54. * @param mixed[] $regions
  55. * An array keyed by region name, with each element containing the 'height',
  56. * 'width', and 'x' and 'y' offsets of each region.
  57. * @param int $width
  58. * The width of the SVG.
  59. * @param int $height
  60. * The height of the SVG.
  61. * @param int|null $stroke_width
  62. * The width of region borders.
  63. *
  64. * @return array
  65. * A render array representing a SVG icon.
  66. */
  67. protected function buildRenderArray(array $regions, $width, $height, $stroke_width) {
  68. $build = [
  69. '#type' => 'html_tag',
  70. '#tag' => 'svg',
  71. '#attributes' => [
  72. 'width' => $width,
  73. 'height' => $height,
  74. 'class' => [
  75. 'layout-icon',
  76. ],
  77. ],
  78. ];
  79. if ($this->id) {
  80. $build['#attributes']['class'][] = Html::getClass('layout-icon--' . $this->id);
  81. }
  82. if ($this->label) {
  83. $build['title'] = [
  84. '#type' => 'html_tag',
  85. '#tag' => 'title',
  86. '#value' => $this->label,
  87. ];
  88. }
  89. // Append each polygon to the SVG.
  90. foreach ($regions as $region => $attributes) {
  91. // Wrapping with a <g> element allows for metadata to exist alongside the
  92. // rectangle.
  93. $build['region'][$region] = [
  94. '#type' => 'html_tag',
  95. '#tag' => 'g',
  96. ];
  97. $build['region'][$region]['title'] = [
  98. '#type' => 'html_tag',
  99. '#tag' => 'title',
  100. '#value' => $region,
  101. ];
  102. // Assemble the rectangle SVG element.
  103. $build['region'][$region]['rect'] = [
  104. '#type' => 'html_tag',
  105. '#tag' => 'rect',
  106. '#attributes' => [
  107. 'x' => $attributes['x'],
  108. 'y' => $attributes['y'],
  109. 'width' => $attributes['width'],
  110. 'height' => $attributes['height'],
  111. 'stroke-width' => $stroke_width,
  112. 'class' => [
  113. 'layout-icon__region',
  114. Html::getClass('layout-icon__region--' . $region),
  115. ],
  116. ],
  117. ];
  118. }
  119. return $build;
  120. }
  121. /**
  122. * Calculates the dimensions and offsets of all regions.
  123. *
  124. * @param string[][] $rows
  125. * A two-dimensional array representing the visual output of the layout. See
  126. * the documentation for the $icon_map parameter of
  127. * \Drupal\Core\Layout\Icon\IconBuilderInterface::build().
  128. * @param int $width
  129. * The width of the SVG.
  130. * @param int $height
  131. * The height of the SVG.
  132. * @param int $stroke_width
  133. * The width of region borders.
  134. * @param int $padding
  135. * The padding between regions.
  136. *
  137. * @return mixed[][]
  138. * An array keyed by region name, with each element containing the 'height',
  139. * 'width', and 'x' and 'y' offsets of each region.
  140. */
  141. protected function calculateSvgValues(array $rows, $width, $height, $stroke_width, $padding) {
  142. $region_rects = [];
  143. $row_height = $this->getLength(count($rows), $height, $stroke_width, $padding);
  144. foreach ($rows as $row => $cols) {
  145. $column_width = $this->getLength(count($cols), $width, $stroke_width, $padding);
  146. $vertical_offset = $this->getOffset($row, $row_height, $stroke_width, $padding);
  147. foreach ($cols as $col => $region) {
  148. $horizontal_offset = $this->getOffset($col, $column_width, $stroke_width, $padding);
  149. // Check if this region is new, or already exists in the rectangle.
  150. if (!isset($region_rects[$region])) {
  151. $region_rects[$region] = [
  152. 'x' => $horizontal_offset,
  153. 'y' => $vertical_offset,
  154. 'width' => $column_width,
  155. 'height' => $row_height,
  156. ];
  157. }
  158. else {
  159. // In order to include the area of the previous region and any padding
  160. // or border, subtract the calculated offset from the original offset.
  161. $region_rects[$region]['width'] = $column_width + ($horizontal_offset - $region_rects[$region]['x']);
  162. $region_rects[$region]['height'] = $row_height + ($vertical_offset - $region_rects[$region]['y']);
  163. }
  164. }
  165. }
  166. return $region_rects;
  167. }
  168. /**
  169. * Gets the offset for this region.
  170. *
  171. * @param int $delta
  172. * The zero-based delta of the region.
  173. * @param int $length
  174. * The height or width of the region.
  175. * @param int $stroke_width
  176. * The width of the region borders.
  177. * @param int $padding
  178. * The padding between regions.
  179. *
  180. * @return int
  181. * The offset for this region.
  182. */
  183. protected function getOffset($delta, $length, $stroke_width, $padding) {
  184. // Half of the stroke width is drawn outside the dimensions.
  185. $stroke_width /= 2;
  186. // For every region in front of this add two strokes, as well as one
  187. // directly in front.
  188. $num_of_strokes = 2 * $delta + 1;
  189. return ($num_of_strokes * $stroke_width) + ($delta * ($length + $padding));
  190. }
  191. /**
  192. * Gets the height or width of a region.
  193. *
  194. * @param int $number_of_regions
  195. * The total number of regions.
  196. * @param int $length
  197. * The total height or width of the icon.
  198. * @param int $stroke_width
  199. * The width of the region borders.
  200. * @param int $padding
  201. * The padding between regions.
  202. *
  203. * @return float|int
  204. * The height or width of a region.
  205. */
  206. protected function getLength($number_of_regions, $length, $stroke_width, $padding) {
  207. if ($number_of_regions === 0) {
  208. return 0;
  209. }
  210. // Half of the stroke width is drawn outside the dimensions.
  211. $total_stroke = $number_of_regions * $stroke_width;
  212. // Padding does not precede the first region.
  213. $total_padding = ($number_of_regions - 1) * $padding;
  214. // Divide the remaining length by the number of regions.
  215. return ($length - $total_padding - $total_stroke) / $number_of_regions;
  216. }
  217. /**
  218. * {@inheritdoc}
  219. */
  220. public function setId($id) {
  221. $this->id = $id;
  222. return $this;
  223. }
  224. /**
  225. * {@inheritdoc}
  226. */
  227. public function setLabel($label) {
  228. $this->label = $label;
  229. return $this;
  230. }
  231. /**
  232. * {@inheritdoc}
  233. */
  234. public function setWidth($width) {
  235. $this->width = $width;
  236. return $this;
  237. }
  238. /**
  239. * {@inheritdoc}
  240. */
  241. public function setHeight($height) {
  242. $this->height = $height;
  243. return $this;
  244. }
  245. /**
  246. * {@inheritdoc}
  247. */
  248. public function setPadding($padding) {
  249. $this->padding = $padding;
  250. return $this;
  251. }
  252. /**
  253. * {@inheritdoc}
  254. */
  255. public function setStrokeWidth($stroke_width) {
  256. $this->strokeWidth = $stroke_width;
  257. return $this;
  258. }
  259. }