3 namespace Drupal\Core\Layout\Icon;
5 use Drupal\Component\Utility\Html;
8 * Builds SVG layout icons.
10 class SvgIconBuilder implements IconBuilderInterface {
13 * The machine name of the layout.
20 * The label of the layout.
27 * The width of the SVG.
31 protected $width = 125;
34 * The height of the SVG.
38 protected $height = 150;
41 * The padding between regions.
45 protected $padding = 4;
48 * The width of region borders.
52 protected $strokeWidth = 1;
57 public function build(array $icon_map) {
58 $regions = $this->calculateSvgValues($icon_map, $this->width, $this->height, $this->strokeWidth, $this->padding);
59 return $this->buildRenderArray($regions, $this->width, $this->height, $this->strokeWidth);
63 * Builds a render array representation of an SVG.
65 * @param mixed[] $regions
66 * An array keyed by region name, with each element containing the 'height',
67 * 'width', and 'x' and 'y' offsets of each region.
69 * The width of the SVG.
71 * The height of the SVG.
72 * @param int|null $stroke_width
73 * The width of region borders.
76 * A render array representing a SVG icon.
78 protected function buildRenderArray(array $regions, $width, $height, $stroke_width) {
80 '#type' => 'html_tag',
92 $build['#attributes']['class'][] = Html::getClass('layout-icon--' . $this->id);
97 '#type' => 'html_tag',
99 '#value' => $this->label,
103 // Append each polygon to the SVG.
104 foreach ($regions as $region => $attributes) {
105 // Wrapping with a <g> element allows for metadata to exist alongside the
107 $build['region'][$region] = [
108 '#type' => 'html_tag',
112 $build['region'][$region]['title'] = [
113 '#type' => 'html_tag',
118 // Assemble the rectangle SVG element.
119 $build['region'][$region]['rect'] = [
120 '#type' => 'html_tag',
123 'x' => $attributes['x'],
124 'y' => $attributes['y'],
125 'width' => $attributes['width'],
126 'height' => $attributes['height'],
127 'stroke-width' => $stroke_width,
129 'layout-icon__region',
130 Html::getClass('layout-icon__region--' . $region),
140 * Calculates the dimensions and offsets of all regions.
142 * @param string[][] $rows
143 * A two-dimensional array representing the visual output of the layout. See
144 * the documentation for the $icon_map parameter of
145 * \Drupal\Core\Layout\Icon\IconBuilderInterface::build().
147 * The width of the SVG.
149 * The height of the SVG.
150 * @param int $stroke_width
151 * The width of region borders.
152 * @param int $padding
153 * The padding between regions.
156 * An array keyed by region name, with each element containing the 'height',
157 * 'width', and 'x' and 'y' offsets of each region.
159 protected function calculateSvgValues(array $rows, $width, $height, $stroke_width, $padding) {
162 $row_height = $this->getLength(count($rows), $height, $stroke_width, $padding);
163 foreach ($rows as $row => $cols) {
164 $column_width = $this->getLength(count($cols), $width, $stroke_width, $padding);
165 $vertical_offset = $this->getOffset($row, $row_height, $stroke_width, $padding);
166 foreach ($cols as $col => $region) {
167 $horizontal_offset = $this->getOffset($col, $column_width, $stroke_width, $padding);
169 // Check if this region is new, or already exists in the rectangle.
170 if (!isset($region_rects[$region])) {
171 $region_rects[$region] = [
172 'x' => $horizontal_offset,
173 'y' => $vertical_offset,
174 'width' => $column_width,
175 'height' => $row_height,
179 // In order to include the area of the previous region and any padding
180 // or border, subtract the calculated offset from the original offset.
181 $region_rects[$region]['width'] = $column_width + ($horizontal_offset - $region_rects[$region]['x']);
182 $region_rects[$region]['height'] = $row_height + ($vertical_offset - $region_rects[$region]['y']);
187 return $region_rects;
191 * Gets the offset for this region.
194 * The zero-based delta of the region.
196 * The height or width of the region.
197 * @param int $stroke_width
198 * The width of the region borders.
199 * @param int $padding
200 * The padding between regions.
203 * The offset for this region.
205 protected function getOffset($delta, $length, $stroke_width, $padding) {
206 // Half of the stroke width is drawn outside the dimensions.
208 // For every region in front of this add two strokes, as well as one
209 // directly in front.
210 $num_of_strokes = 2 * $delta + 1;
211 return ($num_of_strokes * $stroke_width) + ($delta * ($length + $padding));
215 * Gets the height or width of a region.
217 * @param int $number_of_regions
218 * The total number of regions.
220 * The total height or width of the icon.
221 * @param int $stroke_width
222 * The width of the region borders.
223 * @param int $padding
224 * The padding between regions.
227 * The height or width of a region.
229 protected function getLength($number_of_regions, $length, $stroke_width, $padding) {
230 if ($number_of_regions === 0) {
234 // Half of the stroke width is drawn outside the dimensions.
235 $total_stroke = $number_of_regions * $stroke_width;
236 // Padding does not precede the first region.
237 $total_padding = ($number_of_regions - 1) * $padding;
238 // Divide the remaining length by the number of regions.
239 return ($length - $total_padding - $total_stroke) / $number_of_regions;
245 public function setId($id) {
253 public function setLabel($label) {
254 $this->label = $label;
261 public function setWidth($width) {
262 $this->width = $width;
269 public function setHeight($height) {
270 $this->height = $height;
277 public function setPadding($padding) {
278 $this->padding = $padding;
285 public function setStrokeWidth($stroke_width) {
286 $this->strokeWidth = $stroke_width;