diff --git a/rsrc/remarkup-image.css b/rsrc/remarkup-image.css
--- a/rsrc/remarkup-image.css
+++ b/rsrc/remarkup-image.css
@@ -2,7 +2,62 @@
  * @provides diagram-remarkup-image-css
  */
 
-.diagram-container > .diagram-content {
+.diagram-content {
+  display: flex;
+}
+
+.diagram-content.diagram-center {
+  justify-content: center;
+}
+
+.diagram-content.diagram-right {
+  justify-content: right;
+}
+
+.diagram-content.diagram-float-left {
+  float: left;
+  margin-right: 1em;
+}
+
+.diagram-content.diagram-float-right {
+  float: right;
+  margin-left: 1em;
+}
+
+.diagram-content > .diagram-container {
   max-width: 100%;
-  cursor: pointer;
+  overflow: hidden !important;
+  flex-grow: 0;
+  flex-shrink: 0;
+}
+
+.diagram-content > .diagram-container.full {
+  flex-basis: 100%;
+}
+
+/* GraphViewer is only rendering when container has width */
+.diagram-content > .diagram-container:empty {
+  min-width: 1px;
+}
+
+/* Fixing images in GraphViewer lightbox toolbar */
+.geDiagramContainer + .geAdaptiveAsset + div > span > img {
+  display: initial;
+}
+
+/* Fixing centering of button in GraphViewer toolbar */
+body > div[style*="align-items:"]:not([class]) {
+  line-height: initial;
+  display: flex;
+}
+
+/* reset some styles for correct svg rendering */
+
+.diagram-content > .diagram-container, .geDiagramContainer {
+  line-height: initial;
+}
+
+.diagram-content > .diagram-container td,
+.geDiagramContainer td {
+  padding: revert-layer;
 }
diff --git a/rsrc/remarkup-image.js b/rsrc/remarkup-image.js
--- a/rsrc/remarkup-image.js
+++ b/rsrc/remarkup-image.js
@@ -4,24 +4,57 @@
 
 JX.onload(function() {
 
-  var singleClickTimeout = null;
-
-  JX.Stratcom.listen(
-    'click',
-    ['diagram-remarkup-image'],
-    function(evt) {
-      var detail = evt.getRawEvent().detail;
-
-      if (detail === 1) {
-        singleClickTimeout = window.setTimeout(function() {
-          window.open('/diagram/data/' + evt.getTarget().dataset.diagramVersion);
-          singleClickTimeout = null;
-        }, 300);
-      } else if (detail === 2) {
-        window.clearTimeout(singleClickTimeout);
-        window.open('/diagram/DIAG' + evt.getTarget().dataset.diagramId);
+  var viewerScriptURI = 'https://viewer.diagrams.net/js/viewer-static.min.js';
+  var viewerScriptPrepended = false;
+  var viewerScriptBlocked = false;
+
+  document.addEventListener('securitypolicyviolation', function(e) {
+    if (viewerScriptURI === e.blockedURI) {
+      viewerScriptBlocked = true;
+      processElements();
+    }
+  });
+
+  function processViewer(viewer) {
+    viewer.toolbar.appendChild(
+      viewer.createToolbarButton(
+        function() { window.open(viewer.graphConfig.edit); },
+        'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE0LjA2IDkuMDJsLjkyLjkyTDUuOTIgMTlINXYtLjkybDkuMDYtOS4wNk0xNy42NiAzYy0uMjUgMC0uNTEuMS0uNy4yOWwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODNjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMi0uMi0uNDUtLjI5LS43MS0uMjl6bS0zLjYgMy4xOUwzIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1eiIvPjwvc3ZnPg==',
+        null,
+        true
+      )
+    );
+  }
+
+  function processElements() {
+    document.querySelectorAll('.diagram-container:empty').forEach(function(container) {
+      if (window.GraphViewer) {
+        GraphViewer.createViewerForElement(container, processViewer);
+      } else if (viewerScriptBlocked) {
+        container.innerHTML = '<em>Diagram Viewer could not be loaded.</em>';
       }
+    });
+  }
+
+  function init() {
+    if (!document.querySelector('.diagram-container')) {
+      return;
     }
-  );
 
+    if (viewerScriptPrepended || viewerScriptBlocked) {
+      processElements();
+      return;
+    }
+
+    var viewerScript = document.createElement('script');
+    viewerScript.setAttribute('src', viewerScriptURI);
+    viewerScript.addEventListener('load', processElements);
+
+    document.body.prepend(viewerScript);
+
+    viewerScriptPrepended = true;
+  }
+
+  init();
+  JX.Request.listen('done', function() { setTimeout(init); });
 });
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -26,6 +26,7 @@
     'DiagramTransactionType' => 'xaction/DiagramTransactionType.php',
     'DiagramUploadConduitAPIMethod' => 'conduit/DiagramUploadConduitAPIMethod.php',
     'DiagramVersion' => 'storage/DiagramVersion.php',
+    'DrawioPngParser' => 'parser/DrawioPngParser.php',
     'PhabricatorDiagramQuery' => 'query/PhabricatorDiagramQuery.php',
     'PhabricatorDiagramTransactionQuery' => 'query/PhabricatorDiagramTransactionQuery.php',
     'PhabricatorDiagramVersionQuery' => 'query/PhabricatorDiagramVersionQuery.php',
@@ -61,6 +62,7 @@
       'PhabricatorDestructibleInterface',
       'PhabricatorPolicyInterface',
     ),
+    'DrawioPngParser' => 'Phobject',
     'PhabricatorDiagramQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorDiagramTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorDiagramVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
diff --git a/src/application/DiagramApplication.php b/src/application/DiagramApplication.php
--- a/src/application/DiagramApplication.php
+++ b/src/application/DiagramApplication.php
@@ -1,6 +1,11 @@
 <?php
 
 final class DiagramApplication extends PhabricatorApplication {
+  public function __construct() {
+    CelerityAPI::getStaticResourceResponse()
+      ->addContentSecurityPolicyURI('script-src', 'https://viewer.diagrams.net');
+  }
+
   public function getName() {
     return pht('Diagrams');
   }
diff --git a/src/celerity/map.php b/src/celerity/map.php
--- a/src/celerity/map.php
+++ b/src/celerity/map.php
@@ -11,15 +11,15 @@
     'diagram-extension.css' => '7ad39d5d',
     'iframe-toolbtn.css' => '35ad6f49',
     'iframe-toolbtn.js' => '26d75a35',
-    'remarkup-image.css' => '42b46bf1',
-    'remarkup-image.js' => '64e7e9e1',
+    'remarkup-image.css' => 'fb24906a',
+    'remarkup-image.js' => 'c8b8433e',
   ),
   'symbols' => array(
     'diagram-css-extension' => '7ad39d5d',
     'diagram-css-iframe-toolbtn' => '35ad6f49',
     'diagram-js-iframe-toolbtn' => '26d75a35',
-    'diagram-remarkup-image-css' => '42b46bf1',
-    'diagram-remarkup-image-js' => '64e7e9e1',
+    'diagram-remarkup-image-css' => 'fb24906a',
+    'diagram-remarkup-image-js' => 'c8b8433e',
     'javelin-behavior-diagram-extension' => 'a0b36dca',
   ),
   'requires' => array(),
diff --git a/src/controller/DiagramController.php b/src/controller/DiagramController.php
--- a/src/controller/DiagramController.php
+++ b/src/controller/DiagramController.php
@@ -232,53 +232,19 @@
    * They are cut out before the 2 strings are compared.
    */
   public static function equalPngMetaData($base64_1, $base64_2) {
-    $base64 = array($base64_1, $base64_2);
-    $textData = array();
-    for ($i = 0; $i < 2; $i++) {
-      $data = base64_decode($base64[$i]);
-      $fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
-      $sig = fread($fp, 8);
-      if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
-        fclose($fp);
-        return false;
-      }
-      $textData[$i] = array();
-      while (!feof($fp)) {
-        try {
-          $chunk = unpack('Nlength/a4type', fread($fp, 8));
-        } catch (Exception $e) {
-          // invalid base64 data
-          return false;
-        }
-        if ($chunk['type'] == 'IEND') break;
-        if ($chunk['type'] == 'tEXt') {
-          list($key, $val) = explode("\0", fread($fp, $chunk['length']));
-          if ($key == 'mxfile') {
-            // Decode the URL-encoded XML data
-            $decodedVal = urldecode($val);
-            // Load the XML and remove the modified and etag attributes
-            $xml = simplexml_load_string($decodedVal);
-            unset($xml->attributes()->modified);
-            unset($xml->attributes()->etag);
-            // Save the modified XML as the value
-            $val = $xml->asXML();
-          }
-          $textData[$i][$key] = $val;
-          fseek($fp, 4, SEEK_CUR);
-        } else {
-          fseek($fp, $chunk['length'] + 4, SEEK_CUR);
-        }
-      }
-      fclose($fp);
-    }
+    $mxfile1 = DrawioPngParser::getMxfile($base64_1);
+    $mxfile2 = DrawioPngParser::getMxfile($base64_2);
 
-    if (isset($textData[0]['mxfile']) && isset($textData[1]['mxfile'])) {
-      // Both arrays contain the mxfile key, compare their values
-      return $textData[0]['mxfile'] == $textData[1]['mxfile'];
-    } else {
-      // At least one of the arrays doesn't contain mxfile key, return false
+    if ($mxfile1 === null || $mxfile2 === null) {
       return false;
     }
+
+    unset($mxfile1->attributes()->modified);
+    unset($mxfile1->attributes()->etag);
+    unset($mxfile2->attributes()->modified);
+    unset($mxfile2->attributes()->etag);
+
+    return $mxfile1->asXML() == $mxfile2->asXML();
   }
 
   /**
diff --git a/src/parser/DrawioPngParser.php b/src/parser/DrawioPngParser.php
new file mode 100644
--- /dev/null
+++ b/src/parser/DrawioPngParser.php
@@ -0,0 +1,38 @@
+<?php
+
+class DrawioPngParser extends Phobject {
+
+  static public function getMxfile($base64Data) {
+    $fp = fopen('data://text/plain;base64,' . $base64Data, 'rb');
+    $sig = fread($fp, 8);
+
+    if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
+      fclose($fp);
+      return null;
+    }
+
+    while (!feof($fp)) {
+      try {
+        $chunk = unpack('Nlength/a4type', fread($fp, 8));
+      } catch (Exception $e) {
+        // invalid base64 data
+        return null;
+      }
+      if ($chunk['type'] == 'IEND') break;
+      if ($chunk['type'] == 'tEXt') {
+        list($key, $val) = explode("\0", fread($fp, $chunk['length']));
+        if ($key == 'mxfile') {
+          fclose($fp);
+          // Decode the URL-encoded XML data
+          return simplexml_load_string(urldecode($val), null, LIBXML_NOBLANKS);
+        }
+        fseek($fp, 4, SEEK_CUR);
+      } else {
+        fseek($fp, $chunk['length'] + 4, SEEK_CUR);
+      }
+    }
+
+    fclose($fp);
+    return null;
+  }
+}
diff --git a/src/remarkup/PhabricatorRemarkupDiagramRule.php b/src/remarkup/PhabricatorRemarkupDiagramRule.php
--- a/src/remarkup/PhabricatorRemarkupDiagramRule.php
+++ b/src/remarkup/PhabricatorRemarkupDiagramRule.php
@@ -11,10 +11,6 @@
     return 'DIAG';
   }
 
-  public function getRuleVersion() {
-    return '1.0';
-  }
-
   protected function loadObjects(array $ids) {
     $viewer = $this->getEngine()->getConfig('viewer');
 
@@ -31,70 +27,71 @@
     PhabricatorObjectHandle $handle,
     $options) {
 
-    if ($options) {
-      $params = explode(',', $options);
-      $params = array_map('trim', $params);
-    } else {
-      $params = array();
+    $options = $this->getOptions($options);
+    $fullSize = !!$options['full'];
+    $hasSize = $fullSize;
+    $canFloat = false;
+    $contentClass = 'diagram-content';
+    $widthStyle = '';
+
+    switch ($options['layout'] && !$fullSize) {
+      case 'right':
+      case 'center':
+        $contentClass .= ' diagram-' . $options['layout'];
+        break;
+      case 'left':
+      default:
+        $contentClass .= ' diagram-left';
+        break;
     }
 
-    // Generate the appropriate HTML using the data from the Diagram and
-    // file objects.
-    $style = '';
-    $class = 'diagram-content';
-    $alt = '';
-
-    $has_layout = false;
-    foreach ($params as $param) {
-      if (strpos($param, '=') !== false) {
-        list($key, $value) = explode('=', $param, 2);
-      } else {
-        $key = $param;
-        $value = null;
-      }
-      switch ($key) {
-        case 'layout':
-          $has_layout = true;
-          if ($value === 'left') {
-            $class .= ' phabricator-remarkup-embed-layout-left';
-          } else if ($value === 'right') {
-            $class .= ' phabricator-remarkup-embed-layout-right';
-          }
+    if ($options['float'] && !$fullSize) {
+      $canFloat = true;
+      switch ($options['float']) {
+        case 'right':
+          $contentClass .= ' diagram-float-right';
           break;
-        case 'float':
-          $class .= ' phabricator-remarkup-embed-float-left';
-          break;
-        case 'size':
-          if ($value === 'full') {
-            $style .= 'width: 100%;';
-          }
-          break;
-        case 'alt':
-          $alt = phutil_escape_html($value);
+        case 'left':
+        default:
+          $contentClass .= ' diagram-float-left';
           break;
       }
     }
 
-    if ($has_layout == false) {
-      $class .= ' phabricator-remarkup-embed-layout-left';
+    $width = $options['width'];
+    if ($width && !$fullSize && preg_match('/^(?:\d*\\.)?\d+(%|px)?$/', $width)) {
+      $hasSize = true;
+      if (is_numeric($width)) {
+        $widthStyle = 'width: ' . $width . 'px;';
+      } else {
+        $widthStyle = 'width: ' . $width . ';';
+      }
     }
 
+    $mxGraphConfig = array(
+      'responsive' => $hasSize,
+      'resize' => true,
+      'editable' => true,
+      'edit' => PhabricatorEnv::getURI('/diagram/DIAG' . $diagram->getDiagramID()),
+      'lightbox' => true,
+      'toolbar' => 'pages' . ($hasSize ? '' : ' zoom'),
+      'toolbar-position' => 'inline',
+      'page' => $options['page'] - 1,
+      'xml' => DrawioPngParser::getMxfile($diagram->getBase64Data())->asXML()
+    );
+
     $output = phutil_tag(
       'div',
       array(
-        'class' => 'diagram-container',
+        'class' => $contentClass,
+        'style' => $canFloat ? $widthStyle : '',
       ),
       phutil_tag(
-        'img',
+        'div',
         array(
-          'style' => $style,
-          'class' => $class,
-          'src' => 'data:image/png;base64,' . $diagram->getBase64Data(),
-          'alt' => $alt,
-          'data-sigil' => 'diagram-remarkup-image',
-          'data-diagram-version' => $diagram->getPHID(),
-          'data-diagram-id' => $diagram->getDiagramID(),
-          'title' => 'Double click to edit...'
+          'class' => 'diagram-container' . (($fullSize || $canFloat) ? ' full' : ''),
+          'style' => !$canFloat ? $widthStyle : '',
+          'data-mxgraph' => json_encode($mxGraphConfig)
         )
       )
     );
@@ -102,6 +99,24 @@
     return $output;
   }
 
+  private function getOptions($option_string) {
+    $options = array(
+      'full' => false,
+      'layout' => 'left',
+      'float' => '',
+      'width' => '',
+      'page' => 1
+    );
+
+    if ($option_string) {
+      $option_string = trim($option_string, ', ');
+      $parser = new PhutilSimpleOptions();
+      $options = $parser->parse($option_string) + $options;
+    }
+
+    return $options;
+  }
+
   public function getDocumentation() {
     return <<<EOT
 = Diagrams
@@ -111,13 +126,22 @@
 
 You can create and store diagrams in your Phorge instance.
 These diagrams can be referenced by means of the `DIAG` token.
-For example: `{DIAG123}`.
-
-When you double click on a diagram, the editor will open the corresponding
-diagram in a new browser tab.
-
-You can create diagrams with multiple pages, but only the first one will be
-visualized in a referenced diagram.
+For example: `{DIAG123, full, page=2}`.
+
+When you hover over on a diagram, an edit button will appear which opens
+the editor of the corresponding diagram in a new browser tab.
+
+When you click on a diagram, a modal will open to view the diagram
+standalone.
+
+The rule supports following options:
+- **layout**: left (default), center, right
+- **float**: If float is set to left or right, the diagram will be floated
+so text wraps around it. Layout will be ignored.
+- **width**: Can only be a number (interpreted as pixel) or an css px/%
+value.
+- **full**: If full is set, layout, float and width are ignored.
+- **page**: Which page of the diagram should be rendered initially. (default: 1)
 
 When you modify an existing diagram, a new version will be created.
 You can select older versions in the editor by means of the dropdown in the