From 88f9b750e36e3b0263202456cad55c370ab900ca Mon Sep 17 00:00:00 2001
From: WidgetsBurritos <davidstinemetze@gmail.com>
Date: Fri, 28 Apr 2017 10:15:16 -0400
Subject: [PATCH] Issue #2694535 by tstoeckler: Add render elements and theme
 functions for creating dynamic SVGs

---
 core/includes/theme.inc                            | 85 ++++++++++++++++++++++
 core/lib/Drupal/Core/Render/Element/Svg.php        | 47 ++++++++++++
 core/lib/Drupal/Core/Render/Element/SvgG.php       | 57 +++++++++++++++
 core/modules/system/templates/svg-g.html.twig      | 19 +++++
 core/modules/system/templates/svg-rect.html.twig   | 30 ++++++++
 core/modules/system/templates/svg.html.twig        | 27 +++++++
 .../Drupal/Tests/Core/Render/Element/SvgGTest.php  | 26 +++++++
 .../Drupal/Tests/Core/Render/Element/SvgTest.php   | 26 +++++++
 core/themes/bartik/bartik.theme                    | 30 ++++++++
 9 files changed, 347 insertions(+)
 create mode 100644 core/lib/Drupal/Core/Render/Element/Svg.php
 create mode 100644 core/lib/Drupal/Core/Render/Element/SvgG.php
 create mode 100644 core/modules/system/templates/svg-g.html.twig
 create mode 100644 core/modules/system/templates/svg-rect.html.twig
 create mode 100644 core/modules/system/templates/svg.html.twig
 create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/SvgGTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/SvgTest.php

diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 4cc6424f3e..9e557139b8 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1713,6 +1713,72 @@ function _field_multiple_value_form_sort_helper($a, $b) {
 }
 
 /**
+ * Prepares variables for SVG templates.
+ *
+ * Default template: svg.html.twig
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - children: Children to render.
+ */
+function template_preprocess_svg(array &$variables) {
+  $element = $variables['element'];
+  $variables['width'] = $element['#width'];
+  $variables['height'] = $element['#height'];
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for SVG group templates.
+ *
+ * Default template: svg-g.html.twig
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - children: Children to render.
+ */
+function template_preprocess_svg_g(array &$variables) {
+  $element = $variables['element'];
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for SVG group templates.
+ *
+ * Default template: svg-rect.html.twig
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - attributes: Additional element attributes.
+ *   - fill: Fills with a color.
+ *   - stroke: Stroke color.
+ *   - stroke_width: Width of strokes.
+ *   - x: Horizontal offset.
+ *   - y: Vertical offset.
+ */
+function template_preprocess_svg_rect(array &$variables) {
+  $variables['attributes'] += ['style' => ''];
+  if (!empty($variables['attributes']['style'])) {
+    $styles = explode(';', $variables['attributes']['style']);
+  }
+  else {
+    $styles = [];
+  }
+
+  if ($variables['fill']) {
+    $styles[] = 'fill:' . $variables['fill'];
+  }
+  if ($variables['stroke']) {
+    $styles[] = 'stroke:' . $variables['stroke'];
+    if ($variables['stroke_width']) {
+      $styles[] = 'stroke-width:' . $variables['stroke_width'];
+    }
+  }
+
+  $variables['attributes']['style'] = implode(';', $styles);
+}
+
+/**
  * Provides theme registration for themes across .inc files.
  */
 function drupal_common_theme() {
@@ -1864,5 +1930,24 @@ function drupal_common_theme() {
     'field_multiple_value_form' => [
       'render element' => 'element',
     ],
+    'svg' => [
+      'render element' => 'element',
+    ],
+    'svg_g' => [
+      'render element' => 'element',
+    ],
+    'svg_rect' => [
+      'variables' => [
+        'x' => 0,
+        'y' => 0,
+        'width' => 0,
+        'height' => 0,
+        'title' => '',
+        'fill' => NULL,
+        'stroke' => NULL,
+        'stroke_width' => NULL,
+        'attributes' => [],
+      ],
+    ],
   ];
 }
diff --git a/core/lib/Drupal/Core/Render/Element/Svg.php b/core/lib/Drupal/Core/Render/Element/Svg.php
new file mode 100644
index 0000000000..dba6ba4bae
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Svg.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\Html as HtmlUtility;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a SVG element.
+ *
+ * Usage example:
+ * @code
+ * $form['icon'] = [
+ *   '#type' => 'svg',
+ *   '#width' => 32,
+ *   '#height' => 32,
+ * ];
+ * $form['icon']['rect'] = [
+ *   '#theme' => 'svg_rect',
+ *   '#x' => 8,
+ *   '#y' => 8,
+ *   '#width' => 16,
+ *   '#height' => 16,
+ * ];
+ * @endcode
+ *
+ * @RenderElement("svg")
+ */
+class Svg extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return [
+      '#process' => [
+        [$class, 'processGroup'],
+      ],
+      '#pre_render' => [
+        [$class, 'preRenderGroup'],
+      ],
+      '#theme_wrappers' => ['svg'],
+    ];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/SvgG.php b/core/lib/Drupal/Core/Render/Element/SvgG.php
new file mode 100644
index 0000000000..d748f38682
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/SvgG.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\Html as HtmlUtility;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a SVG element.
+ *
+ * Usage example:
+ * @code
+ * $form['icon'] = [
+ *   '#type' => 'svg',
+ *   '#width' => 32,
+ *   '#height' => 32,
+ * ];
+ * $form['icon']['g'] = [
+ *   '#type' => 'svg_g',
+ * ];
+ * $form['icon']['g']['top_left'] = [
+ *   '#theme' => 'svg_rect',
+ *   '#x' => 0,
+ *   '#y' => 0,
+ *   '#width' => 16,
+ *   '#height' => 16,
+ * ];
+ * $form['icon']['g']['bottom_right'] = [
+ *   '#theme' => 'svg_rect',
+ *   '#x' => 16,
+ *   '#y' => 16,
+ *   '#width' => 16,
+ *   '#height' => 16,
+ * ];
+ * @endcode
+ *
+ * @RenderElement("svg_g")
+ */
+class SvgG extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return [
+      '#process' => [
+        [$class, 'processGroup'],
+      ],
+      '#pre_render' => [
+        [$class, 'preRenderGroup'],
+      ],
+      '#theme_wrappers' => ['svg_g'],
+    ];
+  }
+
+}
diff --git a/core/modules/system/templates/svg-g.html.twig b/core/modules/system/templates/svg-g.html.twig
new file mode 100644
index 0000000000..d587931482
--- /dev/null
+++ b/core/modules/system/templates/svg-g.html.twig
@@ -0,0 +1,19 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a SVG g element.
+ *
+ * Available variables:
+ * - attributes: Any attributes to add to the SVG element.
+ * - children: Any children elements.
+ *
+ * @see template_preprocess_svg_g()
+ *
+ * @ingroup themeable
+ */
+#}
+<g{{ attributes }}>
+  {% if children %}
+    {{ children }}
+  {% endif %}
+</g>
diff --git a/core/modules/system/templates/svg-rect.html.twig b/core/modules/system/templates/svg-rect.html.twig
new file mode 100644
index 0000000000..e31f3f4380
--- /dev/null
+++ b/core/modules/system/templates/svg-rect.html.twig
@@ -0,0 +1,30 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a SVG rect element.
+ *
+ * Available variables:
+ * - attributes: Any attributes to add to the SVG element.
+ * - children: Any children elements.
+ * - element: Any element attributes.
+ *
+ * @see template_preprocess_svg_rect()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set attributes = attributes
+    .setAttribute('x', x|default(10))
+    .setAttribute('y', y|default(10))
+    .setAttribute('width', width|default(100))
+    .setAttribute('height', height|default(100))
+%}
+<rect{{ attributes }}>
+  {% if title %}
+    <title>{{ title }}</title>
+  {% endif %}
+  {% if children %}
+    {{ children }}
+  {% endif %}
+</rect>
diff --git a/core/modules/system/templates/svg.html.twig b/core/modules/system/templates/svg.html.twig
new file mode 100644
index 0000000000..476e83df84
--- /dev/null
+++ b/core/modules/system/templates/svg.html.twig
@@ -0,0 +1,27 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a SVG container element.
+ *
+ * Available variables:
+ * - width: The width of the SVG element.
+ * - height: The height of the SVG element.
+ * - attributes: Any attributes to add to the SVG element.
+ * - children: Any children elements.
+ *
+ * @see template_preprocess_svg()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set attributes = attributes
+    .setAttribute('xmlns', "http://www.w3.org/2000/svg")
+    .setAttribute('width', width)
+    .setAttribute('height', height)
+%}
+<svg{{ attributes }}>
+  {% if children %}
+    {{ children }}
+  {% endif %}
+</svg>
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/SvgGTest.php b/core/tests/Drupal/Tests/Core/Render/Element/SvgGTest.php
new file mode 100644
index 0000000000..37678533c6
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/Element/SvgGTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Tests\Core\Render\Element;
+
+use Drupal\Core\Render\Markup;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Render\Element\SvgG;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Element\SvgG
+ * @group Render
+ */
+class SvgGTest extends UnitTestCase {
+
+  /**
+   * @covers ::getInfo
+   */
+  public function testGetInfo() {
+    $svg = new SvgG([], 'test', 'test');
+    $info = $svg->getInfo();
+    $this->assertArrayHasKey('#process', $info);
+    $this->assertArrayHasKey('#pre_render', $info);
+    $this->assertArrayHasKey('#theme_wrappers', $info);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/SvgTest.php b/core/tests/Drupal/Tests/Core/Render/Element/SvgTest.php
new file mode 100644
index 0000000000..4c50c66852
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/Element/SvgTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Tests\Core\Render\Element;
+
+use Drupal\Core\Render\Markup;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Render\Element\Svg;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Element\Svg
+ * @group Render
+ */
+class SvgTest extends UnitTestCase {
+
+  /**
+   * @covers ::getInfo
+   */
+  public function testGetInfo() {
+    $svg = new Svg([], 'test', 'test');
+    $info = $svg->getInfo();
+    $this->assertArrayHasKey('#process', $info);
+    $this->assertArrayHasKey('#pre_render', $info);
+    $this->assertArrayHasKey('#theme_wrappers', $info);
+  }
+
+}
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index 1c408cf3cc..35e33da743 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -115,6 +115,36 @@ function bartik_theme_suggestions_form_alter(array &$suggestions, array $variabl
  * Implements hook_form_alter() to add classes to the search form.
  */
 function bartik_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+   $form['icon'] = [
+     '#type' => 'svg',
+     '#width' => 32,
+     '#height' => 32,
+   ];
+   $form['icon']['rect'] = [
+     '#theme' => 'svg_rect',
+     '#x' => 8,
+     '#y' => 8,
+     '#width' => 16,
+     '#height' => 16,
+   ];
+   $form['icon']['g'] = [
+     '#type' => 'svg_g',
+   ];
+   $form['icon']['g']['top_left'] = [
+     '#theme' => 'svg_rect',
+     '#x' => 0,
+     '#y' => 0,
+     '#width' => 16,
+     '#height' => 16,
+   ];
+   $form['icon']['g']['bottom_right'] = [
+     '#theme' => 'svg_rect',
+     '#x' => 16,
+     '#y' => 16,
+     '#width' => 16,
+     '#height' => 16,
+   ];
+
   if (in_array($form_id, ['search_block_form', 'search_form'])) {
     $key = ($form_id == 'search_block_form') ? 'actions' : 'basic';
     if (!isset($form[$key]['submit']['#attributes'])) {
-- 
2.11.0 (Apple Git-81)

