e7218b3c29f9b8e682328d3fd0f5fa034331e008
[yaffs-website] / web / core / lib / Drupal / Core / Layout / Icon / SvgIconBuilder.php
1 <?php
2
3 namespace Drupal\Core\Layout\Icon;
4
5 use Drupal\Component\Utility\Html;
6
7 /**
8  * Builds SVG layout icons.
9  */
10 class SvgIconBuilder implements IconBuilderInterface {
11
12   /**
13    * The machine name of the layout.
14    *
15    * @var string
16    */
17   protected $id;
18
19   /**
20    * The label of the layout.
21    *
22    * @var string
23    */
24   protected $label;
25
26   /**
27    * The width of the SVG.
28    *
29    * @var int
30    */
31   protected $width = 125;
32
33   /**
34    * The height of the SVG.
35    *
36    * @var int
37    */
38   protected $height = 150;
39
40   /**
41    * The padding between regions.
42    *
43    * @var int
44    */
45   protected $padding = 4;
46
47   /**
48    * The width of region borders.
49    *
50    * @var int|null
51    */
52   protected $strokeWidth = 1;
53
54   /**
55    * {@inheritdoc}
56    */
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);
60   }
61
62   /**
63    * Builds a render array representation of an SVG.
64    *
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.
68    * @param int $width
69    *   The width of the SVG.
70    * @param int $height
71    *   The height of the SVG.
72    * @param int|null $stroke_width
73    *   The width of region borders.
74    *
75    * @return array
76    *   A render array representing a SVG icon.
77    */
78   protected function buildRenderArray(array $regions, $width, $height, $stroke_width) {
79     $build = [
80       '#type' => 'html_tag',
81       '#tag' => 'svg',
82       '#attributes' => [
83         'width' => $width,
84         'height' => $height,
85         'class' => [
86           'layout-icon',
87         ],
88       ],
89     ];
90
91     if ($this->id) {
92       $build['#attributes']['class'][] = Html::getClass('layout-icon--' . $this->id);
93     }
94
95     if ($this->label) {
96       $build['title'] = [
97         '#type' => 'html_tag',
98         '#tag' => 'title',
99         '#value' => $this->label,
100       ];
101     }
102
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
106       // rectangle.
107       $build['region'][$region] = [
108         '#type' => 'html_tag',
109         '#tag' => 'g',
110       ];
111
112       $build['region'][$region]['title'] = [
113         '#type' => 'html_tag',
114         '#tag' => 'title',
115         '#value' => $region,
116       ];
117
118       // Assemble the rectangle SVG element.
119       $build['region'][$region]['rect'] = [
120         '#type' => 'html_tag',
121         '#tag' => 'rect',
122         '#attributes' => [
123           'x' => $attributes['x'],
124           'y' => $attributes['y'],
125           'width' => $attributes['width'],
126           'height' => $attributes['height'],
127           'stroke-width' => $stroke_width,
128           'class' => [
129             'layout-icon__region',
130             Html::getClass('layout-icon__region--' . $region),
131           ],
132         ],
133       ];
134     }
135
136     return $build;
137   }
138
139   /**
140    * Calculates the dimensions and offsets of all regions.
141    *
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().
146    * @param int $width
147    *   The width of the SVG.
148    * @param int $height
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.
154    *
155    * @return mixed[][]
156    *   An array keyed by region name, with each element containing the 'height',
157    *   'width', and 'x' and 'y' offsets of each region.
158    */
159   protected function calculateSvgValues(array $rows, $width, $height, $stroke_width, $padding) {
160     $region_rects = [];
161
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);
168
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,
176           ];
177         }
178         else {
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']);
183         }
184       }
185     }
186
187     return $region_rects;
188   }
189
190   /**
191    * Gets the offset for this region.
192    *
193    * @param int $delta
194    *   The zero-based delta of the region.
195    * @param int $length
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.
201    *
202    * @return int
203    *   The offset for this region.
204    */
205   protected function getOffset($delta, $length, $stroke_width, $padding) {
206     // Half of the stroke width is drawn outside the dimensions.
207     $stroke_width /= 2;
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));
212   }
213
214   /**
215    * Gets the height or width of a region.
216    *
217    * @param int $number_of_regions
218    *   The total number of regions.
219    * @param int $length
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.
225    *
226    * @return float|int
227    *   The height or width of a region.
228    */
229   protected function getLength($number_of_regions, $length, $stroke_width, $padding) {
230     if ($number_of_regions === 0) {
231       return 0;
232     }
233
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;
240   }
241
242   /**
243    * {@inheritdoc}
244    */
245   public function setId($id) {
246     $this->id = $id;
247     return $this;
248   }
249
250   /**
251    * {@inheritdoc}
252    */
253   public function setLabel($label) {
254     $this->label = $label;
255     return $this;
256   }
257
258   /**
259    * {@inheritdoc}
260    */
261   public function setWidth($width) {
262     $this->width = $width;
263     return $this;
264   }
265
266   /**
267    * {@inheritdoc}
268    */
269   public function setHeight($height) {
270     $this->height = $height;
271     return $this;
272   }
273
274   /**
275    * {@inheritdoc}
276    */
277   public function setPadding($padding) {
278     $this->padding = $padding;
279     return $this;
280   }
281
282   /**
283    * {@inheritdoc}
284    */
285   public function setStrokeWidth($stroke_width) {
286     $this->strokeWidth = $stroke_width;
287     return $this;
288   }
289
290 }