Repository: nadar/quill-delta-parser (php)

Issue:
Image height and width is not getting rendered
**Describe the bug**

When the height and width attributes appear in an image, they do not get rendered in the HTML.

**The delta code which generates the Problem**

Example:
```json
{"ops":[{"attributes":{"height":"141","width":"210","color":"#000000","background":"transparent"},"insert":{"image":"https://lh6.googleusercontent.com/XhTtDRQlxIzcbEk-lQ38XDV0p4Gg7A0vYrP-r6huUZXyoGDMdKcb7jkl5i31BfQPGAhK3Yk_AEUrw1e8fEdCxWLVCl2UCzPHoAXDg4HqK_KlUGH4IdnZKyPqVLkyCm5PFFJ-AAEEclxnQX7jvnxm7aw"}},{"insert":"\n"}]}
```

**The expected html output the delta should produce**

Example (Expected)

```html
<p><span style="color:#000000"><img src="https://lh6.googleusercontent.com/XhTtDRQlxIzcbEk-lQ38XDV0p4Gg7A0vYrP-r6huUZXyoGDMdKcb7jkl5i31BfQPGAhK3Yk_AEUrw1e8fEdCxWLVCl2UCzPHoAXDg4HqK_KlUGH4IdnZKyPqVLkyCm5PFFJ-AAEEclxnQX7jvnxm7aw" height="141px" width="210px" alt="" class="img-responsive img-fluid" /></span></p>
```

Actual:
```html
<p><span style="color:#000000"><img src="https://lh6.googleusercontent.com/XhTtDRQlxIzcbEk-lQ38XDV0p4Gg7A0vYrP-r6huUZXyoGDMdKcb7jkl5i31BfQPGAhK3Yk_AEUrw1e8fEdCxWLVCl2UCzPHoAXDg4HqK_KlUGH4IdnZKyPqVLkyCm5PFFJ-AAEEclxnQX7jvnxm7aw" alt="" class="img-responsive img-fluid" /></span></p>
```

**Expected behavior**
`height="141px" width="210px"` should appear when `"height":"141","width":"210"` appears in the attributes.

**Screenshots**
Actual:
![image](https://user-images.githubusercontent.com/20955511/183215549-a96d0a16-510f-46f1-b0a1-49dd804adf29.png)
Expected:
![image](https://user-images.githubusercontent.com/20955511/183215608-8423d8df-2798-4d71-9d6a-199be83f850d.png)

**Additional context**
These attributes appear in quill after pasting from a Google doc with a resized image.

Pull request:
image size
closes #70 

Patch:
diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache
index 31f780a..7795f2c 100644
--- a/.php-cs-fixer.cache
+++ b/.php-cs-fixer.cache
@@ -1,1 +1,1 @@
-{"php":"7.4.30","version":"3.9.4:v3.9.4#093466b1c3ff3e3b469bdbc9827bb2362de6b2fa","indent":"    ","lineEnding":"\n","rules":{"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_import_per_statement":{"group_to_single_imports":false},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"no_unused_imports":true},"hashes":{"src\/listener\/Blockquote.php":3980380124,"src\/listener\/Italic.php":1313329063,"src\/listener\/Mention.php":285055241,"src\/listener\/Strike.php":2779945597,"src\/listener\/Image.php":1733879840,"src\/listener\/Bold.php":187641664,"src\/listener\/Heading.php":3130184963,"src\/listener\/Color.php":2766846014,"src\/listener\/Script.php":3340331114,"src\/listener\/Align.php":1533193015,"src\/listener\/Font.php":3324966128,"src\/listener\/Text.php":3051037862,"src\/listener\/Video.php":665588134,"src\/listener\/Link.php":3874932253,"src\/listener\/Lists.php":1842448793,"src\/listener\/Underline.php":582324479,"src\/listener\/CodeBlock.php":684575651,"src\/BlockListener.php":2650047619,"src\/Line.php":673683854,"src\/Pick.php":1648661170,"src\/Debug.php":513125652,"src\/Lexer.php":741989628,"src\/Listener.php":3833391286,"src\/InlineListener.php":1204237746,"output.php":697163598}}
\ No newline at end of file
+{"php":"7.4.30","version":"3.9.5:v3.9.5#4465d70ba776806857a1ac2a6f877e582445ff36","indent":"    ","lineEnding":"\n","rules":{"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_import_per_statement":{"group_to_single_imports":false},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"no_unused_imports":true},"hashes":{"src\/listener\/Blockquote.php":3980380124,"src\/listener\/Italic.php":1313329063,"src\/listener\/Mention.php":285055241,"src\/listener\/Strike.php":2779945597,"src\/listener\/Image.php":4263841528,"src\/listener\/Bold.php":187641664,"src\/listener\/Heading.php":3130184963,"src\/listener\/Color.php":2766846014,"src\/listener\/Script.php":3340331114,"src\/listener\/Align.php":1533193015,"src\/listener\/Font.php":3324966128,"src\/listener\/Text.php":3051037862,"src\/listener\/Video.php":665588134,"src\/listener\/Link.php":1917770009,"src\/listener\/Lists.php":211467228,"src\/listener\/Underline.php":582324479,"src\/listener\/CodeBlock.php":684575651,"src\/BlockListener.php":2650047619,"src\/Line.php":1027652124,"src\/Pick.php":3046107625,"src\/Debug.php":513125652,"src\/Lexer.php":3041155537,"src\/Listener.php":3833391286,"src\/InlineListener.php":1204237746,"output.php":697163598}}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3262de7..f732e70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 
 This project adheres to [Semantic Versioning](http://semver.org/).
 
+## 3.2.0
+
++ [#71](https://github.com/nadar/quill-delta-parser/pull/71) Added image width and height attributes to the image tag if available.
+
 ## 3.1.0 (24. July 2022)
 
 + [#68](https://github.com/nadar/quill-delta-parser/pull/68) Deprecated the magical getter `$pick->$name` in `nadar\quill\Pick` class, use `$pick->optionValue($name)` instead.
diff --git a/composer.lock b/composer.lock
index d8284c2..5f4539a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -446,16 +446,16 @@
         },
         {
             "name": "friendsofphp/php-cs-fixer",
-            "version": "v3.9.4",
+            "version": "v3.9.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
-                "reference": "093466b1c3ff3e3b469bdbc9827bb2362de6b2fa"
+                "reference": "4465d70ba776806857a1ac2a6f877e582445ff36"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/093466b1c3ff3e3b469bdbc9827bb2362de6b2fa",
-                "reference": "093466b1c3ff3e3b469bdbc9827bb2362de6b2fa",
+                "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/4465d70ba776806857a1ac2a6f877e582445ff36",
+                "reference": "4465d70ba776806857a1ac2a6f877e582445ff36",
                 "shasum": ""
             },
             "require": {
@@ -523,7 +523,7 @@
             "description": "A tool to automatically fix PHP code style",
             "support": {
                 "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
-                "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.9.4"
+                "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.9.5"
             },
             "funding": [
                 {
@@ -531,7 +531,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-07-15T21:04:49+00:00"
+            "time": "2022-07-22T08:43:51+00:00"
         },
         {
             "name": "myclabs/deep-copy",
@@ -1340,16 +1340,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "8.5.27",
+            "version": "8.5.28",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "df70070f2711b8fe8dcca0797c1239ede8c94be6"
+                "reference": "8f2d1c9c7b30382459c871467853da1a6e44fbd4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/df70070f2711b8fe8dcca0797c1239ede8c94be6",
-                "reference": "df70070f2711b8fe8dcca0797c1239ede8c94be6",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8f2d1c9c7b30382459c871467853da1a6e44fbd4",
+                "reference": "8f2d1c9c7b30382459c871467853da1a6e44fbd4",
                 "shasum": ""
             },
             "require": {
@@ -1418,7 +1418,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.27"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.28"
             },
             "funding": [
                 {
@@ -1430,7 +1430,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-06-19T12:11:16+00:00"
+            "time": "2022-07-29T09:20:50+00:00"
         },
         {
             "name": "psr/cache",
@@ -2360,16 +2360,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v5.4.10",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000"
+                "reference": "535846c7ee6bc4dd027ca0d93220601456734b10"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000",
-                "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000",
+                "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10",
+                "reference": "535846c7ee6bc4dd027ca0d93220601456734b10",
                 "shasum": ""
             },
             "require": {
@@ -2439,7 +2439,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v5.4.10"
+                "source": "https://github.com/symfony/console/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -2455,7 +2455,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-06-26T13:00:04+00:00"
+            "time": "2022-07-22T10:42:43+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
@@ -2690,16 +2690,16 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v5.4.9",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba"
+                "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba",
-                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd",
+                "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd",
                 "shasum": ""
             },
             "require": {
@@ -2734,7 +2734,7 @@
             "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v5.4.9"
+                "source": "https://github.com/symfony/filesystem/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -2750,20 +2750,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-05-20T13:55:35+00:00"
+            "time": "2022-07-20T13:00:38+00:00"
         },
         {
             "name": "symfony/finder",
-            "version": "v5.4.8",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9"
+                "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9",
-                "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c",
+                "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c",
                 "shasum": ""
             },
             "require": {
@@ -2797,7 +2797,7 @@
             "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v5.4.8"
+                "source": "https://github.com/symfony/finder/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -2813,20 +2813,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-15T08:07:45+00:00"
+            "time": "2022-07-29T07:37:50+00:00"
         },
         {
             "name": "symfony/options-resolver",
-            "version": "v5.4.3",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/options-resolver.git",
-                "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8"
+                "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/cc1147cb11af1b43f503ac18f31aa3bec213aba8",
-                "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8",
+                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690",
+                "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690",
                 "shasum": ""
             },
             "require": {
@@ -2866,7 +2866,7 @@
                 "options"
             ],
             "support": {
-                "source": "https://github.com/symfony/options-resolver/tree/v5.4.3"
+                "source": "https://github.com/symfony/options-resolver/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -2882,7 +2882,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-02T09:53:40+00:00"
+            "time": "2022-07-20T13:00:38+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
@@ -3457,16 +3457,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v5.4.8",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3"
+                "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
-                "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
+                "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1",
+                "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1",
                 "shasum": ""
             },
             "require": {
@@ -3499,7 +3499,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v5.4.8"
+                "source": "https://github.com/symfony/process/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -3515,7 +3515,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-08T05:07:18+00:00"
+            "time": "2022-06-27T16:58:25+00:00"
         },
         {
             "name": "symfony/service-contracts",
@@ -3664,16 +3664,16 @@
         },
         {
             "name": "symfony/string",
-            "version": "v5.4.10",
+            "version": "v5.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097"
+                "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097",
-                "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097",
+                "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322",
+                "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322",
                 "shasum": ""
             },
             "require": {
@@ -3730,7 +3730,7 @@
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v5.4.10"
+                "source": "https://github.com/symfony/string/tree/v5.4.11"
             },
             "funding": [
                 {
@@ -3746,7 +3746,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-06-26T15:57:47+00:00"
+            "time": "2022-07-24T16:15:25+00:00"
         },
         {
             "name": "theseer/tokenizer",
diff --git a/src/Line.php b/src/Line.php
index 3a21089..b8bdb16 100644
--- a/src/Line.php
+++ b/src/Line.php
@@ -214,11 +214,12 @@ class Line
      * Get the value for a given attribute name, if not exists return false.
      *
      * @param string $name
+     * @param mixed $defaultValue (@since 3.2.0)
      * @return mixed
      */
-    public function getAttribute($name)
+    public function getAttribute($name, $defaultValue = false)
     {
-        return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : false;
+        return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $defaultValue;
     }
 
     /**
diff --git a/src/listener/Image.php b/src/listener/Image.php
index 941c15d..b08fd39 100644
--- a/src/listener/Image.php
+++ b/src/listener/Image.php
@@ -16,7 +16,7 @@ class Image extends InlineListener
     /**
      * @var string
      */
-    public $wrapper = '<img src="{src}" alt="" class="img-responsive img-fluid" />';
+    public $wrapper = '<img src="{src}" {width} {height} alt="" class="img-responsive img-fluid" />';
 
     /**
      * {@inheritDoc}
@@ -25,7 +25,23 @@ class Image extends InlineListener
     {
         $embedUrl = $line->insertJsonKey('image');
         if ($embedUrl) {
-            $this->updateInput($line, str_replace(['{src}'], [$line->getLexer()->escape($embedUrl)], $this->wrapper));
+            if ($width = $line->getAttribute('width')) {
+                $width = 'width="'.$line->getLexer()->escape($width).'"';
+            }
+
+            if ($height = $line->getAttribute('height')) {
+                $height = 'height="'.$line->getLexer()->escape($height).'"';
+            }
+
+            $this->updateInput($line, preg_replace('/\s+/', ' ', str_replace([
+                '{src}',
+                '{width}',
+                '{height}'
+            ], [
+                $line->getLexer()->escape($embedUrl),
+                $width,
+                $height
+            ], $this->wrapper)));
         }
     }
 }


Test patch:
diff --git a/tests/Issue70Test.php b/tests/Issue70Test.php
new file mode 100644
index 0000000..8118945
--- /dev/null
+++ b/tests/Issue70Test.php
@@ -0,0 +1,28 @@
+<?php
+namespace nadar\quill\tests;
+
+class Issue70Test extends DeltaTestCase
+{
+    public $json = <<<'JSON'
+{
+  "ops": [
+    {
+      "attributes": {
+        "height": "141",
+        "width": "210"
+      },
+      "insert": {
+        "image": "https://localhost/test.jpg"
+      }
+    },
+    {
+      "insert": "\n"
+    }
+  ]
+}
+JSON;
+
+    public $html = <<<'EOT'
+    <p><img src="https://localhost/test.jpg" width="210" height="141" alt="" class="img-responsive img-fluid" /></p>
+EOT;
+}