From 0d9e7430bc4e030a9ac6dd7318716beccf2a3728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 14 May 2026 08:14:25 +0700 Subject: [PATCH 1/7] refactor: decouple native clipboard and math plugins from default wrapper to fix Android build issues --- README.md | 69 ++++++++++++++++++++++++++++---------- example/pubspec.lock | 23 ------------- lib/hyper_render.dart | 8 ++--- pubspec.yaml | 18 +++------- pubspec_publish_ready.yaml | 2 -- 5 files changed, 58 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index a2318c6..0ffaa76 100644 --- a/README.md +++ b/README.md @@ -53,21 +53,6 @@ HyperViewer( Zero configuration. XSS sanitization is **on by default**. -> **Android note:** `hyper_render` depends on `super_clipboard` which transitively pulls in `irondash_engine_context`. That library was compiled against Android SDK 31, but its `androidx.fragment:1.7.1` dependency requires `compileSdk โ‰ฅ 34`. Add this one-time workaround to your `android/build.gradle.kts`: -> -> ```kotlin -> // android/build.gradle.kts (root โ€” not app/build.gradle.kts) -> subprojects { -> afterEvaluate { -> extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply { -> compileSdk = 35 -> } -> } -> } -> ``` -> -> This overrides `compileSdk` for all library sub-projects so AGP's `checkAarMetadata` passes. Tracked in [#5](https://github.com/brewkits/hyper_render/issues/5). - --- ## ๐Ÿ—๏ธ Why Switch? The Architecture Argument @@ -403,14 +388,64 @@ HTML / Markdown / Quill Delta | Package | pub.dev | Description | |---------|---------|-------------| -| [`hyper_render`](https://pub.dev/packages/hyper_render) | [![pub](https://img.shields.io/pub/v/hyper_render.svg)](https://pub.dev/packages/hyper_render) | Convenience wrapper โ€” one dependency, everything included | +| [`hyper_render`](https://pub.dev/packages/hyper_render) | [![pub](https://img.shields.io/pub/v/hyper_render.svg)](https://pub.dev/packages/hyper_render) | Convenience wrapper โ€” HTML, Markdown, Delta, syntax highlight | | [`hyper_render_core`](https://pub.dev/packages/hyper_render_core) | [![pub](https://img.shields.io/pub/v/hyper_render_core.svg)](https://pub.dev/packages/hyper_render_core) | Core engine โ€” UDT model, CSS resolver, RenderObject | | [`hyper_render_html`](https://pub.dev/packages/hyper_render_html) | [![pub](https://img.shields.io/pub/v/hyper_render_html.svg)](https://pub.dev/packages/hyper_render_html) | HTML + CSS parser | | [`hyper_render_markdown`](https://pub.dev/packages/hyper_render_markdown) | [![pub](https://img.shields.io/pub/v/hyper_render_markdown.svg)](https://pub.dev/packages/hyper_render_markdown) | Markdown adapter (GFM) | | [`hyper_render_highlight`](https://pub.dev/packages/hyper_render_highlight) | [![pub](https://img.shields.io/pub/v/hyper_render_highlight.svg)](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
` blocks |
-| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Image copy / share |
 | [`hyper_render_devtools`](https://pub.dev/packages/hyper_render_devtools) | [![pub](https://img.shields.io/pub/v/hyper_render_devtools.svg)](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools extension โ€” UDT inspector, computed styles |
 
+### Optional add-ons
+
+These packages have native dependencies and are **not bundled** by default. Install only if you need the feature.
+
+#### `hyper_render_clipboard` โ€” Native image copy / share
+
+```yaml
+dependencies:
+  hyper_render_clipboard: ^1.3.0
+```
+
+```dart
+import 'package:hyper_render_clipboard/hyper_render_clipboard.dart';
+
+HyperViewer(
+  html: html,
+  imageClipboardHandler: SuperClipboardHandler(),
+)
+```
+
+> **Android setup required:** `super_clipboard` transitively pulls in `irondash_engine_context`, which requires `compileSdk โ‰ฅ 34`. Add this to `android/build.gradle.kts` (root file, not `app/`):
+>
+> ```kotlin
+> subprojects {
+>     afterEvaluate {
+>         extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply {
+>             compileSdk = 35
+>         }
+>     }
+> }
+> ```
+>
+> Tracked in [#5](https://github.com/brewkits/hyper_render/issues/5).
+
+#### `hyper_render_math` โ€” LaTeX / MathML rendering
+
+```yaml
+dependencies:
+  hyper_render_math: ^1.3.0
+```
+
+```dart
+import 'package:hyper_render_math/hyper_render_math.dart';
+
+final registry = HyperPluginRegistry()..register(const MathPlugin());
+HyperViewer(html: html, pluginRegistry: registry)
+```
+
+| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Native image copy / share via `super_clipboard` |
+| [`hyper_render_math`](https://pub.dev/packages/hyper_render_math) | [![pub](https://img.shields.io/pub/v/hyper_render_math.svg)](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML via `flutter_math_fork` |
+
 ---
 
 ## Contributing
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 0b9e861..5f5c921 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -222,14 +222,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "5.0.0"
-  flutter_math_fork:
-    dependency: transitive
-    description:
-      name: flutter_math_fork
-      sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407"
-      url: "https://pub.dev"
-    source: hosted
-    version: "0.7.4"
   flutter_svg:
     dependency: transitive
     description:
@@ -402,13 +394,6 @@ packages:
       relative: true
     source: path
     version: "1.3.0"
-  hyper_render_math:
-    dependency: transitive
-    description:
-      path: "../packages/hyper_render_math"
-      relative: true
-    source: path
-    version: "1.3.0"
   irondash_engine_context:
     dependency: transitive
     description:
@@ -846,14 +831,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.7.10"
-  tuple:
-    dependency: transitive
-    description:
-      name: tuple
-      sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.0.2"
   typed_data:
     dependency: transitive
     description:
diff --git a/lib/hyper_render.dart b/lib/hyper_render.dart
index 75b4a66..e0e021d 100644
--- a/lib/hyper_render.dart
+++ b/lib/hyper_render.dart
@@ -194,9 +194,5 @@ export 'src/plugins/default_css_parser.dart' show DefaultCssParser;
 export 'src/plugins/default_code_highlighter.dart'
     show DefaultCodeHighlighter, HighlightTheme;
 
-// ============================================
-// Clipboard plugin
-// ============================================
-
-export 'package:hyper_render_clipboard/hyper_render_clipboard.dart'
-    show SuperClipboardHandler;
+// SuperClipboardHandler is available in the optional hyper_render_clipboard package.
+// Add it to your pubspec if you need native image copy/share.
diff --git a/pubspec.yaml b/pubspec.yaml
index 4fd75b3..5762e68 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -23,12 +23,10 @@ dependencies:
 
   # ============================================
   # MODULAR ARCHITECTURE v1.0
-  # This package is a convenience wrapper that includes all plugins
-  # For minimal dependencies, use individual packages:
-  #   - hyper_render_core: Zero-dep core
-  #   - hyper_render_html: HTML parsing
-  #   - hyper_render_markdown: Markdown parsing
-  #   - hyper_render_highlight: Syntax highlighting
+  # This package is a convenience wrapper that includes core plugins.
+  # Optional add-ons (install separately if needed):
+  #   - hyper_render_clipboard: Image copy/share (adds super_clipboard native dep)
+  #   - hyper_render_math: LaTeX/MathML rendering (adds flutter_math_fork)
   # ============================================
 
   # NOTE: For publishing to pub.dev, replace path dependencies with versions:
@@ -36,8 +34,6 @@ dependencies:
   #   hyper_render_html: ^1.3.0
   #   hyper_render_markdown: ^1.3.0
   #   hyper_render_highlight: ^1.3.0
-  #   hyper_render_clipboard: ^1.3.0
-  #   hyper_render_math: ^1.3.0
 
   hyper_render_core:
     path: packages/hyper_render_core
@@ -51,12 +47,6 @@ dependencies:
   hyper_render_highlight:
     path: packages/hyper_render_highlight
 
-  hyper_render_clipboard:
-    path: packages/hyper_render_clipboard
-
-  hyper_render_math:
-    path: packages/hyper_render_math
-
   # Fixed version constraints for pub.dev compliance
   flutter_highlight: ^0.7.0
   highlight: ^0.7.0
diff --git a/pubspec_publish_ready.yaml b/pubspec_publish_ready.yaml
index a00b0c1..fc89627 100644
--- a/pubspec_publish_ready.yaml
+++ b/pubspec_publish_ready.yaml
@@ -38,8 +38,6 @@ dependencies:
   hyper_render_html: ^1.3.0
   hyper_render_markdown: ^1.3.0
   hyper_render_highlight: ^1.3.0
-  hyper_render_clipboard: ^1.3.0
-  hyper_render_math: ^1.3.0
 
   flutter_highlight: ^0.7.0
   highlight: ^0.7.0

From 77f1076a699e95ef13fbef49be94e1c8a6851da2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?=
 
Date: Thu, 14 May 2026 09:12:35 +0700
Subject: [PATCH 2/7] chore: bump versions to 1.3.1 across all packages, update
 changelogs, and fix markdown test

---
 CHANGELOG.md                                  |   8 ++++++++
 example/pubspec.lock                          |  12 ++++++------
 packages/hyper_render_clipboard/pubspec.yaml  |   2 +-
 packages/hyper_render_core/pubspec.yaml       |   2 +-
 .../devtools_ui/pubspec.yaml                  |   2 +-
 packages/hyper_render_devtools/pubspec.yaml   |   2 +-
 packages/hyper_render_highlight/pubspec.yaml  |   2 +-
 packages/hyper_render_html/pubspec.yaml       |   2 +-
 packages/hyper_render_markdown/pubspec.yaml   |   2 +-
 .../test/markdown_adapter_test.dart           |   5 ++++-
 packages/hyper_render_math/pubspec.yaml       |   2 +-
 pubspec.yaml                                  |  10 +++++-----
 pubspec_publish_ready.yaml                    |  10 +++++-----
 scripts/publish.sh                            |   4 ++--
 test/golden/goldens/blockquote.png            | Bin 1439 -> 2044 bytes
 test/golden/goldens/cjk_kinsoku.png           | Bin 881 -> 1411 bytes
 test/golden/goldens/cjk_ruby.png              | Bin 608 -> 963 bytes
 test/golden/goldens/code_block.png            | Bin 3454 -> 4750 bytes
 test/golden/goldens/definition_list.png       | Bin 1050 -> 1845 bytes
 test/golden/goldens/float_cjk.png             | Bin 2070 -> 2425 bytes
 test/golden/goldens/float_clear.png           | Bin 2235 -> 2784 bytes
 test/golden/goldens/float_left.png            | Bin 2774 -> 3689 bytes
 test/golden/goldens/float_right.png           | Bin 2591 -> 3384 bytes
 test/golden/goldens/full_article.png          | Bin 4610 -> 6060 bytes
 test/golden/goldens/headings.png              | Bin 1534 -> 1450 bytes
 test/golden/goldens/image_placeholder.png     | Bin 1469 -> 1856 bytes
 test/golden/goldens/links.png                 | Bin 701 -> 1215 bytes
 test/golden/goldens/mixed_inline.png          | Bin 3769 -> 5529 bytes
 test/golden/goldens/mixed_lists.png           | Bin 874 -> 1372 bytes
 test/golden/goldens/ordered_list.png          | Bin 910 -> 1432 bytes
 test/golden/goldens/rtl_arabic.png            | Bin 1376 -> 2017 bytes
 test/golden/goldens/rtl_hebrew.png            | Bin 767 -> 1245 bytes
 test/golden/goldens/rtl_mixed.png             | Bin 744 -> 1264 bytes
 test/golden/goldens/table.png                 | Bin 1479 -> 1937 bytes
 test/golden/goldens/table_spans.png           | Bin 1200 -> 1398 bytes
 test/golden/goldens/text_formatting.png       | Bin 2971 -> 4478 bytes
 test/golden/goldens/unordered_list.png        | Bin 1110 -> 1710 bytes
 37 files changed, 38 insertions(+), 27 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e62d46..621d6d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## [1.3.1] - 2026-05-04
+
+### ๐Ÿ—๏ธ Maintenance & Build Fixes
+
+- **Decoupled Native Dependencies**: Removed `hyper_render_clipboard` (which relies on `super_clipboard`) and `hyper_render_math` from the default `hyper_render` wrapper package. This eliminates the `compileSdk = 34` requirement out-of-the-box, fixing Android build failures for users on older Gradle setups.
+- **Optional Add-ons**: Native clipboard and LaTeX math rendering are now opt-in. Install `hyper_render_clipboard` or `hyper_render_math` directly if needed.
+
+
 ## [1.3.0] - 2026-05-03
 
 ### โœจ New Features
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 5f5c921..3857b28 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -358,42 +358,42 @@ packages:
       path: ".."
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   hyper_render_clipboard:
     dependency: "direct main"
     description:
       path: "../packages/hyper_render_clipboard"
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   hyper_render_core:
     dependency: "direct main"
     description:
       path: "../packages/hyper_render_core"
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   hyper_render_highlight:
     dependency: transitive
     description:
       path: "../packages/hyper_render_highlight"
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   hyper_render_html:
     dependency: transitive
     description:
       path: "../packages/hyper_render_html"
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   hyper_render_markdown:
     dependency: transitive
     description:
       path: "../packages/hyper_render_markdown"
       relative: true
     source: path
-    version: "1.3.0"
+    version: "1.3.1"
   irondash_engine_context:
     dependency: transitive
     description:
diff --git a/packages/hyper_render_clipboard/pubspec.yaml b/packages/hyper_render_clipboard/pubspec.yaml
index e374bc0..d3c134f 100644
--- a/packages/hyper_render_clipboard/pubspec.yaml
+++ b/packages/hyper_render_clipboard/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_clipboard
 description: Image clipboard support for HyperRender using super_clipboard. Enables copying, saving, and sharing images.
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_clipboard
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_core/pubspec.yaml b/packages/hyper_render_core/pubspec.yaml
index cd346fd..fdba64f 100644
--- a/packages/hyper_render_core/pubspec.yaml
+++ b/packages/hyper_render_core/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_core
 description: Core engine for HyperRender. Universal Document Tree, single-RenderObject layout with CSS float, Flexbox, Grid, CJK typography, and crash-free text selection.
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_core
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_devtools/devtools_ui/pubspec.yaml b/packages/hyper_render_devtools/devtools_ui/pubspec.yaml
index 0d6414c..c1d85e7 100644
--- a/packages/hyper_render_devtools/devtools_ui/pubspec.yaml
+++ b/packages/hyper_render_devtools/devtools_ui/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_devtools_ui
 description: "DevTools panel UI for HyperRender โ€” tree inspector, style viewer, layout debug."
-version: 1.3.0
+version: 1.3.1
 publish_to: 'none'
 
 environment:
diff --git a/packages/hyper_render_devtools/pubspec.yaml b/packages/hyper_render_devtools/pubspec.yaml
index cc67a64..038f2c9 100644
--- a/packages/hyper_render_devtools/pubspec.yaml
+++ b/packages/hyper_render_devtools/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_devtools
 description: "DevTools extension for HyperRender โ€” inspect UDT trees, computed styles, fragments, and performance metrics."
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_devtools
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_highlight/pubspec.yaml b/packages/hyper_render_highlight/pubspec.yaml
index 14defca..34840d7 100644
--- a/packages/hyper_render_highlight/pubspec.yaml
+++ b/packages/hyper_render_highlight/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_highlight
 description: Syntax highlighting plugin for HyperRender using flutter_highlight. Supports 180+ programming languages with multiple themes.
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_highlight
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_html/pubspec.yaml b/packages/hyper_render_html/pubspec.yaml
index d46ada9..e69c9c1 100644
--- a/packages/hyper_render_html/pubspec.yaml
+++ b/packages/hyper_render_html/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_html
 description: HTML parsing plugin for HyperRender. Converts HTML content to UDT with full CSS support.
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_html
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_markdown/pubspec.yaml b/packages/hyper_render_markdown/pubspec.yaml
index 679e16d..701042d 100644
--- a/packages/hyper_render_markdown/pubspec.yaml
+++ b/packages/hyper_render_markdown/pubspec.yaml
@@ -1,6 +1,6 @@
 name: hyper_render_markdown
 description: Markdown parser plugin for HyperRender - Converts Markdown to Universal Document Tree (UDT).
-version: 1.3.0
+version: 1.3.1
 homepage: https://github.com/brewkits/hyper_render
 repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_markdown
 issue_tracker: https://github.com/brewkits/hyper_render/issues
diff --git a/packages/hyper_render_markdown/test/markdown_adapter_test.dart b/packages/hyper_render_markdown/test/markdown_adapter_test.dart
index 4be10ce..7d588ed 100644
--- a/packages/hyper_render_markdown/test/markdown_adapter_test.dart
+++ b/packages/hyper_render_markdown/test/markdown_adapter_test.dart
@@ -76,7 +76,10 @@ void main() {
       final ul = result.document.children[0] as BlockNode;
       // md package generates 
  • ... expect(ul.children[0], isA()); - expect((ul.children[0] as BlockNode).attributes['data-task'], isNotNull); + final firstChild = (ul.children[0] as BlockNode).children.first; + expect(firstChild, isA()); + expect((firstChild as AtomicNode).tagName, 'input'); + expect(firstChild.attributes['type'], 'checkbox'); }); test('parse line break', () { diff --git a/packages/hyper_render_math/pubspec.yaml b/packages/hyper_render_math/pubspec.yaml index 8743aca..f48a71b 100644 --- a/packages/hyper_render_math/pubspec.yaml +++ b/packages/hyper_render_math/pubspec.yaml @@ -2,7 +2,7 @@ name: hyper_render_math description: > Mathematical formula rendering plugin for HyperRender. Supports LaTeX and MathML using the flutter_math_fork engine. -version: 1.3.0 +version: 1.3.1 homepage: https://github.com/brewkits/hyper_render repository: https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_math issue_tracker: https://github.com/brewkits/hyper_render/issues diff --git a/pubspec.yaml b/pubspec.yaml index 5762e68..6ade52e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: hyper_render description: "Render HTML/Markdown/Delta at 60 FPS. The only Flutter renderer with CSS float layout, crash-free text selection, and CJK Ruby typography. Drop-in flutter_html alternative." -version: 1.3.0 +version: 1.3.1 homepage: https://github.com/brewkits/hyper_render repository: https://github.com/brewkits/hyper_render issue_tracker: https://github.com/brewkits/hyper_render/issues @@ -30,10 +30,10 @@ dependencies: # ============================================ # NOTE: For publishing to pub.dev, replace path dependencies with versions: - # hyper_render_core: ^1.3.0 - # hyper_render_html: ^1.3.0 - # hyper_render_markdown: ^1.3.0 - # hyper_render_highlight: ^1.3.0 + # hyper_render_core: ^1.3.1 + # hyper_render_html: ^1.3.1 + # hyper_render_markdown: ^1.3.1 + # hyper_render_highlight: ^1.3.1 hyper_render_core: path: packages/hyper_render_core diff --git a/pubspec_publish_ready.yaml b/pubspec_publish_ready.yaml index fc89627..7245bdc 100644 --- a/pubspec_publish_ready.yaml +++ b/pubspec_publish_ready.yaml @@ -1,6 +1,6 @@ name: hyper_render description: "Render HTML/Markdown/Delta at 60 FPS. The only Flutter renderer with CSS float layout, crash-free text selection, and CJK Ruby typography. Drop-in flutter_html alternative." -version: 1.3.0 +version: 1.3.1 homepage: https://github.com/brewkits/hyper_render repository: https://github.com/brewkits/hyper_render issue_tracker: https://github.com/brewkits/hyper_render/issues @@ -34,10 +34,10 @@ dependencies: flutter: sdk: flutter - hyper_render_core: ^1.3.0 - hyper_render_html: ^1.3.0 - hyper_render_markdown: ^1.3.0 - hyper_render_highlight: ^1.3.0 + hyper_render_core: ^1.3.1 + hyper_render_html: ^1.3.1 + hyper_render_markdown: ^1.3.1 + hyper_render_highlight: ^1.3.1 flutter_highlight: ^0.7.0 highlight: ^0.7.0 diff --git a/scripts/publish.sh b/scripts/publish.sh index 559aa0c..7b48152 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -# HyperRender publish helper โ€” v1.3.0 +# HyperRender publish helper โ€” v1.3.1 # # Usage: # ./scripts/publish.sh dry-run # verify all packages (no upload) @@ -21,7 +21,7 @@ set -euo pipefail -VERSION="1.3.0" +VERSION="1.3.1" MODE="${1:-dry-run}" # dry-run | publish ROOT="$(cd "$(dirname "$0")/.." && pwd)" diff --git a/test/golden/goldens/blockquote.png b/test/golden/goldens/blockquote.png index e6c312db1cacbc13c1392cc1c2407a084cdd1f13..19b4db22bdb290e3566195adb7ce6076c9d8ced5 100644 GIT binary patch literal 2044 zcmcIl4NOy46n^EeC=}6%L)6Kf&Seuppu$inDvFE&n^0>(s7?kV4rE&7&$d}2V*CM? z&8kQnbt_sdk11Ld@G0HYjX!6p-K?}A+UF>&;-h_g?dyB%$jD+eF=g3FZf@?m$vNkq z^L^hv_;F$^ZK~f?003xl>ti+m0Hw;lg52Hgo)c#mFR*VeyI04hxZADFJ+s1ortIDj zyBcWdp8GohP$h9Ot5S+ijr4t=xRB*nJ^)EI8)DB@57XZ9^`9M|IB$5Z_ZeD6b+iA% zhW4=Nbr&fI6_+!q9v}7pkodG$Qj)ye;~%iMNlCCyr+8u3!I#y z|Bp+9D2y+XT;mrVRwpS_n9RZB+(zy`rs}8BPh{<;-FzOBW=7QQXc8bDn{qt9^Ju_)*&gZv6j zVj^Qpmx|4S3=U^rQ^pKISmSR^KE3ZRc~u+kN9GeN6S(9vUr;PLFO}X}X6S#YW-)8$ z&R4y+85+aXoiNKo$FOl}ck<-*%1ekS?RZ;@rY7QH-r37~28jg}lVgi*4tIMG?M{$q zWInmHsq-!lGoVFAF>x}<_~>~K`5ZB+Rv8viikxc_(r&i3JH29>nSF@9N11|7s~olD zoRms;%)R7g&fqY)UzDZP?`p#WN_kJP8j-j#-T9(sWN2)-6`pmirKRn>Vq|%0E>90* z2wYdY$JtSIOf;Q!`ZG6uHc!MSdV;dMlu&co0|+nM$Guw;2WW3(DK~Bb8(#%RHrk3I zp(>kVo06V*v~mYMYu?T_GBkn7q?e;&t0kC}m{j46r?TAW!j|+}QEgcI;2&m*mt2#F zaw3p?WiaDumpp26?M??r{l=qgbL(Uyp!QHlovous&7#VLF8#Ef%RkrMEZXnt2va85 z8{kLAYxuG#;u&V$V+uV|FO?RY63KeXh$n*}lkuZL6HbZ@^%$<{22opS4}@BV2$RmY zYMPjbn`=~-O0~v|GX%mAWVOk~rxKpp`hp=6<6 zC!B3ot+c&_N>}q#kN>FTc3easf^($S1GkH8<7_+1jul?NE**OO=edmoC2%$opTRi- zqx9q4e~5=_Z=LDu)AkM&&XI9>Bb|J0YPT^rNH<2g`_1cM_~MsS&8BAhmTS{CwH^!E tH6BUNbL(n(Su+~hVDL@O1B@&dd3 z#cY4RzFx+3U?K+xLuUiS1iDdk!u%f;?Oi+X(23u-U%&Xb*Z%*Ni`VntpRfP&Q+}>6 z8ICz=Ci~#y%g3Kze?9&AaES-Qy9YJ*+;|I^7!?Es7=#$nDa*C<4m_=jwr!2AuCJE8 zTmR+j{olW2YyYiz%EIKJq`;uUL<+UZm%XCq*N;C{e?R>(kQHjMwwS*?ka>;+g9i&s zg9`@bw_3em=e;oh#FgK-U%&YG+WQ-?pZ`ohf4+xq9+$rR|KGP=Uq1Jr`*q&uI)7LZ zu5iK{cu>K}e|8hDzITrJ`_n!BZ{mKO-o+LM@U;8j-L{tu_5VM=f4+Vu*U|Ix_m5Xw zB>ulX>)rp7|6%q2F0TFe`@1c+7{qYdq|2{3H%z;)bj$qraW6l36vAx+GCr{1UdH@G zciz^ze?RQ*IX4_KzsQI^imeYW;0p}R!+50Zv{&zOwf9>pd_p=ZIO+uF0T`zy)UcLYS7jN!IU7OFTPE`D24Y*1F Z8G1!qe$89{cp@mGJzf1=);T3K0RYTAqjCTM diff --git a/test/golden/goldens/cjk_kinsoku.png b/test/golden/goldens/cjk_kinsoku.png index cc73778bafa138350065ed00ab6ec11c664890a8..86dbd664ec5004b9c954072ccb242884b81307af 100644 GIT binary patch literal 1411 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCAK+jEl3H%2{XmMP*vT`50|;t3QaXTq#^NA% zCx&(BWL`2bu7WapgMO(y!g}uyU8>9CJ>ekBn;5rvMEk4_HKo&YZsU^x3N2Z-R2>&+_%sEPd;> z_uTwE-IUp&A5`s=U3?;m&ZOj!MTcK7Bz6>l%qys^^{_F9=8{;JfjZ2olp^ZofN zk3R>h`?d0W?UmYihK9(C+iw%w%66~jaxrI`~FmI0R|@?EV!$_*IfV8s>u8M z>bI};eOsC3y+8TpcaUj2zr~8mE0jjt{@JotYirRd{eAo6)?Yur_5G?7x@%UQoBKR! z$BVsUBoht&@z*@)c*;_0hcqVw;<+g6g}&Q~yMRq6P*IaB6RvaxymW+U?Zo zNx!e;1ns*2()$a;VQnJ^rR=D;>l2s0zs}rqFi4G|MWkNt#6si8$>+9w{ae+ra0e5k z!Vv~cyyVCAiqA^xXMKJ9yZrX8EAy7`x3Lenzn)#f>my^(_N#~eZ?96W`>_saEYDJ) z<4+Wy{4QO)Uk>IVIGwPFYwfx6>9fC<-CTOZ&UE&acjZ-k6`>frJ5d83S#vBfhVXJCviwmn?L%H+_&h>1NuuK)PVHO}<)?eFim zZCz>WzZMv#|NYKy=;EKSYpq<}tuWb-hs%L+*8h!XH6)zRbAOWt3&5QTWB5cZyn6Co z(dK)4yG3p8$Zo(+n;o%4TU}%xRf<^vI<;Oh<-Mp{%e_r(LTc5AI`dYR3UwlAK!L| yUIO<`EI5x$jEKC*fcosQJt8y5@`Qreg{zASIrzelF{r5}E*`nQjCC literal 881 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCAK+jEl3H%2{XmMP*vT`50|;t3QaXTq#^NA% zCx&(BWL`2bFiU&7IEGZrd3)C|@34UYi-YzF`=IBnKkHwL@3`hY$;u34bhQzQme60zK z-*7c&vGDQL@AZLTf7ZU~oGc9;OpFSP92hz@1Q?XK5Zo#AnJd3q_J`k(kBf_|yIP|! z%)u~mQ*6Y_Rk0Bab564`Ik+@32m~sixc0-bQ{OzkzS~lDFHzNg8oR~p$uAgOPTgm} zRArQBn|ZqbDdV{$?AC#7fBM|P?Y!m*>*tJT4ze(z7!2X~q^{l-@LufZ-@^ap`IVKG zKTXb?<~(Lq2>NF?;o4-!*PhNmx7wX8)&sg#kQMGi7ZC(k)1KYvmvt^sbLF!HCPpll zEZYXbXV0HiKO=7*<91l`r&eKYTCV9?QY9|e=AnQ=l;$h``@eg^ywQ^ycU4*uVczpFTh5eJbwoJJ%>8{dewHG{s5c*A}drYqWXSuix>1Keubo zgu5HXS@$At$}PM%*W0Z~FY#tl|0RDfP1CbqzuMK-&ENU_Vc5>O)(mS~ zc1pkZ-X$j)^XY_=lf5tR-THTxJ5>7|7&4ABFfI@UIyH<(fFa7T9x5{RL;Yjj<4<=V z{rfwAc~w);6pE04esOL$Gzcu>>rcFPcy>Cg z?A6!D=^^DSFR%ArKmBXVj6=K=DnotMXMg-2y!CBK`Sc*K*jMLQUtM;;?{nG9ufL-2 zRvjuh^sl$v`tIGe^736@A3GQRP<*-BZ@=E&xNE|XIISHRI?e(;I(5VL;`*=3XMt9H zF0S77ekYQOZ(l2HHS76r|Gob5`qJyCtC$e60%p00KdDrZKXvr)<=bkV%g=Y~Yu4U5 z{XOx;zo(zi6|Z&TnecPdEZW-vf64VTt*xi6k9-vq>GWsL7R;uh+(Y+RK#n?{*Tihf4ckV-{0-QTdl6H57w>s zbpEbCzkJ?np}dBkSC1}xZ++Cm{k-^mIWSs2RX*RTr|6d`x_Hyax|4-!-)q9bisdFY dDv9s<$$xospy`d5K9fOy@pScbS?83{1OQjGt&RWy literal 608 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJC*Kn`_Nq4_zjX;W}*vT`50|;t3QaXTq#^NA% zCx&(BWL`2bFmZXhIEGZrd3)E_@34UY+XazA{%dm@e(PH@X8SR%_6n^R`E>K(0^A`eXR1 z!Yh^U$<14@U;Hvyc=K^|@45ItcRtEY&fCM_a;o%k?C-Qq-+a@*{Rmt9S=_$d=6Dh- z!e}%fO)UED5H(+TzU{uNFa8`oFMEDt1Sk;@0tE^Cy;`C(IXPw=11a@%^>bP0l+XkK D1^33$ diff --git a/test/golden/goldens/code_block.png b/test/golden/goldens/code_block.png index 032f78d8f4c52d10c4d6a3d509cd077f5c8671ad..64276127b0143174953406f169bb8444eba407c2 100644 GIT binary patch literal 4750 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 B)0Y4M diff --git a/test/golden/goldens/definition_list.png b/test/golden/goldens/definition_list.png index 549ceeee55ebd96afaf99d53a452c8d6e3436e4e..a512127cb5d88340fa598cae6956c90ff141d9e0 100644 GIT binary patch literal 1845 zcmb7^Yfuwc7=|}#uz?m4@j?wl7_5WD78J|Hifn4JfK!7LxuiyJMYMtv0Z{^BH+CG1 zl`2j_iUu+rt&E^qltC%VWyA(3CYe?dghG%QQ;acz7?!ZPbjt{VAFcHKID5YRapwKL z^Stk=os0-)r&Ue>060fQhBE}WsQzeJ=^)=LgPBq!clZ>3;eRf^TSg&h%@m?16OQJ8RkXqTYxnb)Oghn7K}e`n z?uvGqT2L^q1u``2z=U_Qa|?5_f*71Fy>w~Em|@(g8r!lFb9-g$?~UG}vbt?}od^J$ z550iPWHR7OB+|d*10~*XEsr=yQY1)~C^|vqX+43pP8fG7J&AVR>mH^nk16khFK<5V z4k&hYZLjkvcn}fpQT1Fcz@C|#5XD_XDkN1`w=(HsF~?6P8Py)?HbK-5d3SGl2m0V8 zrx8jQQfV}`+DA`w1DGTde;I+`wT%u$x-MBjS<0mMuZJo#eG6m6PlNY>fijonGP^)P zU%va0dHxhr&zb~+_J?MVERiq?H6n$!7owyd)Oyk|z_qP)c=lrI`N%=x5aYH`hh~}Y z@(Q^;`2%Z_26@p8m&uVSN3<2SA_;PXfs$9|xPWQ?_Nai+yyLp|xkW2_bT~tOV=_uv zMMDv@-x^f({QwW|AS`@h^Kf^01g8uOzNNCkjkb~z*A>%uZ};^&Tn+LO5 z7tboyDowYtVBbWrP;@;Bj@4Oec~DP(FEufZNI(Bpngfi90NsCA#vLw<`D|oED_QtE zT*G`+4y3+oLB%fT%!{c5<2LZ7^W^Shq4!pQ1l+_?oTf``vn7|t$l5p|AiLE{!{Sq2 z>}@6I%<|P__N%a)Q=@||v6>;!kO9K4&Xe6p&-;;2Dt z_P{-yJ}^DM3Y;=5$O5HTQQ}`$C;V!^(T1)7yWVH7@uD2fwdlCm1#6V;xc?acV*~lU zX&?Q(tR$#-P?Qq$Snu{YsllkJ_TNvos~=`J^yg_4w$GTi9#-l<0VDqOQDb5S4%1o@ zweQwE>lWCp8_fa>V`VM}D;EsV&cKFS7tz}=v;BHy$B0iQAs~*5>vK7GsI5E?*vmcP zFr7{<01SzaDY~gGgaxu=3Y4DZR7(aK^ z19sZ`;rtvZgNC7Q3pXu0c%I(o<6)%B^|N{{dGqw{Om3$s2rb~tIne8SXX&tKxKTPM zA?48r%~Mh|47B{4O|3q4WJ3@U3HTq;IFqBk4!B2qN6+6|75fHaJaXrVw9oj> cX&AG-djpkR8p#ex`R7;EHb%I3Yx1{$1N|Zc=Kufz literal 1050 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJC|KMN)l85p)_yZ}HVkgfK4j`!ENa+Cb8H+x?a`}Wn>ZGY}izW4k^xm*AKmCv^~zWn}w$^Tq`hK6b(0R|yP zMg>7)DP#*9_{)6QfBgFQ^RIRA%|f8@Qg{CTow>1o#*BCKbNv=EPvGES=xksho{qYu;&m zW>0{aUGn|!=P%#q--_G+^7Ha{cJ=BP89i878eAM0@KSNNg&A&q`m+1PvOkTmFhzDh zIKXo5s}=(z!aC=Ceb%pk!R7JxY1_Wb@1EnrPD~V6=K43pzT3p4RjpcJhJLL6zgAKHZvkV9M?_3}ruy$v+J`o+87 w&tJYjf2QPrPO*?I!Ei^98f0_P*$)i=v9~O&pZ26S`58#g)78&qol`;+048%dkpKVy diff --git a/test/golden/goldens/float_cjk.png b/test/golden/goldens/float_cjk.png index 484422fb41a27f764d5873f56c230ce7f1e3bf8d..791f9e0bcb485074e8027bc1341e043a7e3cf6b4 100644 GIT binary patch literal 2425 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 literal 2070 zcmb7Fdo&wZ8oxoww4(eac;X z=iZ$F0By8~n-2hxT-E*usHSR_x%0PFOoM-ki+kaRKSD^2RdmNJ2S zp?-sGk|-A_%2s91kI0^zK~$yu7m{`-%(#o^=7<4NIvD3W=4hHRUUZ!xt*e+gQ{&(Q zhYM~BbU*^8Pd$`O-m({NJmB(XQRUtqFB4i`;zj7f3)a6}te=Q%#6j}jxVyXicF$~w-9F6(&y;QytOxHeHN2>D z)q-R!MK>8&Pfn@#8x}tK=#pC&+#T6;Xza1@#R|riai)oZ4=kzvrrmXIk>$UmBvvvG za<$;buwp{1H@m`yz&pE_`|IQK7?X4wvpO(q5E~rvrmG{);uER^7_SOb7p=ao!!rJ| zr$E5F%o#cW=eM#e(s3ORRjzvl4tg{#@a8*s>RCZ_lR4VV#dA0%z%-R@2)fo%9Fe7K z7`+GPnawf+DNUT500Gj`dd~nvZ>KCLjqy-KudLYIz&EP{b$VDB4AFaJy-LL94^UT3 z(_mu~W}q773Uv?+iz@Q{Lkf}?4k`#@kub@XOmrmrgRi{fS=TJk7~kSP)1u`$vFJ6}pST&tF%;ZIH<4TBjk3K=3=QD|A3PY1Rj zRd!P0g(zrQ)&{tODWm=qz5V-~&1KV9S4_zV(&ug~Sp-auX;E(Ha}Hjt9V&S8yg$H3qelJ*8)W$YnyCQ%eEwlZYst1 zY6YR&>yq&p2r^`T5agL_YY!Y*zLsAUQh4jQqIs$?eGL~8caRtcUKp-_i{xhi%{qBj zzxaXsZ0CApjN?j=A+t@5!k27=ybANYWKmvYn0s&Etk>Oqmt>z-qOB@0H}LA8V3L1a zC^iiFF?r62eunB2huWfgRYa#wIjDin`-rCH*gRFclBXf6QfCoc0c7HtfqD*R&mLx= zI)(pvzpAbR%pos?EnN9TN%vhbC7H{VGG-gkk-4U5b~!4KlR&f+C~1TH4#Q;Y66PeJ z@aHE0`gR^6c!(8VOT9ca;8gqU9VZUS6S|z_Hf|~aKA`p_V9U%T{M~8)297|xO>{Jr9tzLxKYypy4L{Y531ZU z@@|don3~Eep+c!CAN9T0xlZ)*l5z<_&)>uC&K1$W;5fh|O&pa(7DKQeMK)Ml$1BK?^~iSMlf0m3OqaD%sD|vH0W@+Pm7}noOoG{ayJ&DfWmm}B(8Xn+%e^4kC z_ngH?t&0Ym0&MEwzuCQ&w+03Tgkzi!IXLvNbpVUI>`)omxNarp+dpdbypAf*eK`Nu z%D#T8&ET1zqE76*-hI!?(lVz<`U!9@xBk|)eXR-07z#bRnp?q)y`5)PH1ZAH#mOnT z(0K0mKPuYz3x0nwqb!pkHah&ineFT3zY_yh<4>rS?^}YKh$hn9;PTPFnkB>k^-|_O l=6U+5#_z}cKRg$|4YBqn-O8}i;=hkWA3x>x81;R|zW~bjx5NMd diff --git a/test/golden/goldens/float_clear.png b/test/golden/goldens/float_clear.png index a676499dbb518067ab19fcd72af1f6a0811ec8cc..872fe7d9d0f41992a84d8de0ae5d477044faeff0 100644 GIT binary patch literal 2784 zcmbtV2~g8l8vjRBR8&N&v|MpR7p<@c6z3;tm-kaZh-+RAb z#^Xn6!+L`E*c$Zr*h`6DH#fzvHW`C-V1C0|{5q0n?mJ^9@7i?BN7M06{fe{+ z7e7>JSgoZ|gkVNC_`DLtnVXxVuq7&KH;3f%;m)l-!3&r-=Hz4fftEDi z;%9hw&(A+-rxdy*O@kYkw%^~y+R~v?snS?17RtgMZ&y@Q)Iav=lEdiuc!iUb)6-al zrdKv2(3VJ2fr5lNnr4UM4oz$6|u zw^QRdC{Sj)rK9(bvzWTP_&Ysm04^+Kk3B%RZ&@hh06DB@oj%rmY6 ziQ`UFFkR3?7?pOQ)-&YQ3IF@*C)2}HYe#frGpP|8Nq=82*#)nEh* z0wg1(x-_Kg7zavmm18JwoM-_6XlyL%%2qp^3TsT(=foFgEMSYWRAMi>YM7s`s^ilZY)}~s2B@F7j?}WF zQ8qb-V|7ptJJnH+A=^xM9xT>$1~D`;y48|M%C)DK+3`SfH$k?KrWfP8x}gYWSNg7~ zrGDyely4wuSnqyD*~T^rJq8hDasnQB!yu4JjVMCu+cudHxAh}!+;!2iv9xwp` zYivQ~`A~rz_|M*U?h?o={vE~D;DuGvJVDzY|dz|sac*@zga8it}b^4F`3Mf zk&)7GTc>oivMGm|%YSmXTh5kF^4=8gQ7V|sOyb%S@3vT(R!IaUFmeIcr0F*FjBLH3 zwzhV$x3`x`i#}bf>o1VWWHgcZ26Hsn%!40+U?KHP(=q;o;#LTy*pdxQj!w z`^DSm&*SMN;lTs#8s~nN%ng4$pIsM1+So*YkMlN9is@tFDAN z)}c(BATRauKIfjRf8*GUHN+#FveQLPS~?totS=iliv6|LG#*BWr&fJf80mTEOjKBk zkIlX-L5DTzO9U{@Cx6x&M~KfMTFi z&bY+h7vhkwrx+fuplcn@>b-{vqsROPOzpD73X;d(#r8XUqQY5QeHLu5QDrR}FVMsW9HpMZxDDIsUr}m>9np5v*{IAPgbm&3dlIRyy{lE3j qVRV`Ywr-qib$k!**HKG}SPQD$d&~`6m)80=9pG~sUg>@An|}epUjw56 literal 2235 zcmb`Hdpy(s9>>3%ihiB+TMtcgN#lne6n-4!x5dwK7jkD3zamL4kqr%VIz8Hk_$8NI zrlwYhOr~YX<@{Jv%5qn79fr+q%r=ZOkMnr+==_e(@1NiMkI&`txx7B__v7<=;?KI+ z?f>q`cK`tFcd)m12LMo(bo^OvkF-7SE{u|PyJD>zJmsW;kqgF2=b%`3J1cid1N^;YG#rVvj{q9)`Rf2QbGQ#i*x*86Z=%r z4A|POLRUXF|G@E{cSN@g*sIJ_9&~(vblWlQY8cA>?%~d0cGCTsRCLZFzwnxN{kqt` zRQtQc=YChP50bM{bhL5@XU4dIn)iGp}~Aj_LAszw3*5s+tQ&q)OyrydDXSJ zZX;07!$sHm;jk;bHo$eO{j@S(7-1z}o6MTHxYW8r`9+=fs0GT`aft}J=% zl$Y$Uz!*`4LSvTbwbI>4U*G(8D(`6C$$&NI|7=rgTU%R;{^gfrEUK#5F=X~$h?26h z^39x_95Fc*WbY-u`mmoXiId7jEQvO$dTEk%Av&>P_-Fxfx4PG3Y>`Dua-$9*@OpiK zK$KcH6zQ$N&g@>qo&aMu+YbeCoTku!ud8(2{Rj&Hm<0XXd1h1CRuEf0b~>Ub=WWo-%A z!_)yy$oIfqIk_*(ck!=sMA62Cv1k=Yh_mjc&Y2gb*{&1wz~xr!hKrF=2iPs|Q<$qk zbPXJH#`FoBB&HwB)S4E4Gz8UFA#h`tRb=M}6l)fm zDpmzT-teIcy)>u1n(^1G>UH&SipuEVnRj{9EuCgC*eY*6S=>Ux@rTeF1X6AU?_;;* z(@TI}i$5iU=XV>8_4!yJKMmmcIX)zKv^rAT#roMZ&rhw2;dvc(pvLyrqEt*NgVE+f zOg}7N8o$KQ2~7Iy_?m$H1f0J&TQv*Uc1H!@XT-8zZI*E(B9s}`EL-vFU%q($fZQ>G!Eee0Gw;~EtUEHvt+W&O`MWg=&j%ksD12-Xnjnre_vBNIvMLJ- z(|BXIO9d0zkQ+i!T)I9V8F zn;yUgon?3?|3hFw)uYmAZS%F-Z#|xnFzgH+RuP`pikD^=mS~Q}8%sjf18FzV!I=nX z#fao2to^jap8MOa?K0SB$L`^O^cuHO?MHu3Lcbs;o4*x~WcvDw#+iZ7lF~lgLq@$# z3`5n6U#UVM{S+bB?|CnQH&BJ^%c~##MBD6txx4wj^E;(Binw-qVvZgo$krjmFe5p` zZw9NvlPSboAo4)#F3onTZynbXLl?VYu1~UYBKm5?J67C^-ufVFBf@`{Xg?(3qqGo z+T#8a9ow;k19HIb3XG}C#$_`g)oM<4QcdT!f69odMXK_*Zx&p-f2w6H`a@(5%NQYR zNpJ)yW!Q!?*-OJ~ukW8I?VPrAa&iKL&pdJjol0DpMwR!jU2GaT+H2Z%by~P?imd2c zsMb?9MeMUAybUDjpE!X)31)jqdI1KKq(e3ke}DhUv6kCMqY|N5Puj*IZz+r#Fk-$o zj6Xym;dFf{7^$`Uq|kUkK!RhYKRiU+m3GQ(qb9j2u%~LucviU1n3|ee4KfgkySlp8 zomnzxZ6ZuP;26>3PY}Zqu3+;8jV36Evnu1vi(Tr6hQ0btv^wMAjfNXN!!A#jIZ|fX z6%zM8kf)7gpu@suex>RypRYuZ1R2mRVDfzp8GEZcrRnSG>z6FuI%VLDnn2C_kX9bn$0RbJQ3pZ#>k!sd$9>u=Ye6Z}TDoXZn8%GH|4H6t&`TU+nzkZL-+n|Z%O z(YHv&C(Gu?Whd*C{5T@R_M+4;Ec;sCfI%(WM_2sb8EJv8>k6mO^e;SPv4UfNF#m$O zZ{5E$DF5ejy-G@sAl-@}h<&1~`Zs1}fiy3#loYK(-A>R>Wc_2guO+a@bG-tB65=YJ zg(?(0PoBU2xF5^57`!AAcnE|g*D2Pr|BvF|*7{llAp3g9rB3$Js6;pWs%?#nW;4vn m*@Xj-{+v4fmzZ>7#BO@iuqRpZ$9B){B?lW9Yl>CS-~SDmapXz> diff --git a/test/golden/goldens/float_left.png b/test/golden/goldens/float_left.png index abdefaaeb84744079bc8dacc69cee321842dc82a..7b5efbc31504dbbe8c7a1dd5914b83849f3f5dda 100644 GIT binary patch literal 3689 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 literal 2774 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 cYVeRRM)?N(gXFP<{9 literal 4610 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 literal 1534 zcmcJPc}&`87{`BFH(b-smNaQ?HFk~1mUPp##(IJpD?^Q%&aACxgH$G7si;^%ejZ(& z>*j$XV;*d^)pk-{ht}c&^_T~8#0wP#XVrm4K}Eodz-Y29TlUYEb>BbU=lQ;QlkbyH zp1h@z;fTWrPaXsS;4t!|pi2N?QDN>Utlu=B=qoi2=3<2lL`GSgMYKkLZuSbrU#S8`MxVc z*TcIT;bM+`O(Ie|yawPsnag}O79OwkoKLoGYjR1%(#4BLS(?d_7CuyBdP-1a=w}Iw zD%1MQtty7>P`QVcJCZ^55!C2qXA2W&&2Q69b!-SoLA!$fI)hQz-~bO|h#`qsPOQ>1MoHho=ACQOJ~V8AQf&lTq+XJ0rD{E# zK-&Ks!n1ezw(Km83pH==)R?_YA)bm4A&(EsM4hCcs@JrJ@dpDtX;@dMQTZ}X5yL@ovk24_)wYIiPk*(szo`HdUVDCDoB}uW zjrI+sgxcLJ*K*yPAhdAy*WIEMlhZH_p`~&dCrW-FQX59S+z9&W!jhL!!kvBhAqte;tsVaJE5D>s@zl6L$NM;Irgq+B3nRT#ISm=N-Sc2$p8E_lUb{eGTe*gzZX ze0xOa4at>N?e{t#^GcRuCWfkE`1%{sv+O~d85*o@t~vT#O>S!qk=X3ol;FZ*J@e+s zX(Z;zcVyoyy}Kx=Md4>G%3J>#-zcN4JowwZkY8w;fPR*EYe|v66T=jZR5zrPBGi*xOJ6yH?``xx)6ki4H(~q%OMTWVciz=E z+CD*=)7rUZNEOjOaf!+Amn?J=lq>zc-FQ>aG}u3_xGX8pfF2P%LbNQ8C5Abq`pg8A zlhI#pq&s1h^M29x9Lmb4W3&lPi*on^i+1K;Vj}dOQa#@Q>fVKyZ@3B^U% U1H;CzXS}*ZUJMUv4ZK$LE4N&NE&u=k diff --git a/test/golden/goldens/image_placeholder.png b/test/golden/goldens/image_placeholder.png index 6ff8ba48842360a53d6d9b7ebe4dc5c2420c8778..dd983316fa9bb38c0d59ffc8f0f40177152d25f1 100644 GIT binary patch delta 1294 zcmY+EeNfT|7{`AoD=QcDa;eQw*KXZ9rYYw(hK-FF4&|l7OB0(n)V4KvE#1vGS*oDMBDHYF%y5-ShABe4g+3dyjP0!0I}D z&a&y0#2x!eTV{2{+I^HwBU1(`={+-J8**JTCT7(geU%TtP`{k7t&$Yava>V3I=Nz_ zZX>OtImJKcP}C9pg=WHI1`ob=JlQ6V7!C~h0>@$6TB!HtXAm}W+w?-EL#ZOwUx=>n zUPe{y#~gV+J7ijPTZ^se1wIJMLTc27Wh9mRmdOR;rr6GegJoK-Q?7PmbQgIU{m15) znueZfZ&dek^r%fVZ0S+k@gB9CnCqG7@hDTZa={(31P!#O2|T_L+|i4?Vpn!t-yXHH zA^tayQtP=gcV0-kA>#qPDS85Fh=AKhUq8rX6 z&Ya+KB}kCPH52Dc=(Z?EkT`8~EPO52#OpJ#l=eIiH@l7d2Q^x+HQe zb!vUzurh{ons0VGmcLNJBtl5~o|`vz@4owbFA_%Lnd=ukf9wvZAoJU5B$ zrg(a@khC`rCoRLvvC3F6iS82)h{J zX1&i)pH1-9PE0s2f$o^bpRT3)527cZhM$VfcYk^nk`PVk<%(2!dN@bT_QMk9Lngf& z&s+V`-pw45)&FRV33hpFC0y3pd#&SUSZFR40YqDMYTtoE6A}sRuj2v8H7{KeY=ox? zknk`vlbSZ+@&{r{f;`@c^hO>B0` z&0?-9t+4W4NME7;E_SwJ_yZ}2NDY!R51q;GlpVF=7Bg)@;PVyh2TK@qHA3o WJhp1R@YtFcTq!#ViCv#CzWE1K#Ge8H delta 888 zcmX@Wx0ic@LlkSer;B4q#hkZy_vXd8${c$*^Q%~ih~^5Nh~)kczE*U$@hEP8|#f-9Q5?`-sy>TzxUS6|2B`6?L*0|W&H2&?EJjtyTXQ( z0`Cv4+PY6)?!)xN-SzJ{C8E9ypLqK8X>-si{>4Xl*bIFye_0pIq5S&aqS)-e2`!ch z2Hku2?)9(YXPjM9|1fXu-n-Ro5`o!o*WNQepmvuz$ZMnLgQIVF+svFC;@;ab@Vx51 z$9zCdnT4f6S7mY|tLS8V)^H%^(>t;0JHwqd?)`FiUwrlV_s{?G>%fjV4h$U}91Mbt zj0%tBPV~*JKL2Rh`;`p04|7fyWHr@#^xdJX=l*AgI59-Pc3io~91|C}4QTy}y!-p^ z*7hH=FJzjW$Z86+ss0291O2#PZ~E!=)gV9epZNFhpa0se=c~>$Drc^>y>tKm|DE4uHh8|Z zb*S7I`R&^4ul-A>lvkZ+Vy-AEC@2WZ-uup$Bj?gzmY{j}b@t?~wXM%sTN~)LG4n>~ z^|f2|qqE<4vfobV5I%8#ZP@DH-A5lS&Yabkz4rLhDbah=o1{ywMpcJ-Spws~x}xI8 zj_-)1bKrmCx7!u`kVIPsO0-djW8?SlEUW!n{`dd=vSRuD_u76aC^Fo3M+Bpi0>flE m7LLj1nPnz!l971RW6v;`cgx#PbLVdarAbd$KbLh*2~7a#X_e^! diff --git a/test/golden/goldens/links.png b/test/golden/goldens/links.png index 9a6fe8d0fe9d2423e60312cd4a34973811266094..209c29a1281ef9f9e9bc7cef695c5a04a635a84e 100644 GIT binary patch literal 1215 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCui#(Q(Vu|2%HhVgedY7H9}xy|R1#?xR`d z6Md&&DiajUjphqJe%Pi(v3?3lXRyJyc&pMG;|d&cdGu8g#$C;tDcaXIqu_BZdG zb#wl+d=9MsxBh^qZ^gbn)z^8{<2V0Y_p|=pVUy1vliU8*N%zmpso(ahJ;?59cz=gp zlE&IU9zXx(l>QPZoPWmZzw-I(bN*)s7+Nwc^J!$5u*iYIBT#{1iG~0}kO&8ZCKpSC z2rH9A7Zam`D;DWc)=OJrkGei{dCD{UX2L)Bb+b4??e5Om zHDz0ON5<_U31xY~jk_c7xaCax`MB=i#?OJa^$U)zU^R-btFV9VCwJ_Q^V;Qyi}&BJ zt2vW$|Ip|DwQ2txWW#5Y>^z|Brv!2@$=scI=4-suS>rve&*f)JBE^$|qHk;ss^exP z*(oKJZ{E>+L*?`1bM|Wc>t}3R#WYjje$V_;b$DGJPyKFR zSv-}KU`SyJ$dy_Xmc4D8e*2Wk)iWQ!MzJHpts#ERO0V_ncmMqP$w*p$QSM{SMKh-= z>CfNzf7@T7*{hwk-u#s{|Nq7R_TR6^_rHBu{q7Ic)k{8Kv0TadJ$-eZ#?#=de7iap zhWwcqSa%5?=T-H`&&Yd~oWK7$+x#{TWG3(7)48k0vNyVinmR`S`v~~B1b+XWuQuJE>=g-!k@?XEFzo`k?Z+|@h z%I*!nHiz0z`LyztGuW4MMNew#w^`M8>^PtD{Pgbfvjv|__x|WV|Kj`Y3Q#)wIrD#E zef_a>*Z0`xH?H;}DWOAse`*Emr88}y)YLpn(j+(BcK@vN72MC$|6M5nl{mP-fsa4= Xckb3?bP0l+XkK#O>+J literal 701 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCui#()Ei(^Q|oVRy2_AYjiXt?-v%Pu*yqBloYYVQ);#<9IPbV8n!&l95> z(>vzV1U|&&{jhERbMoMakBmTr$OJN{O{yACfBjc=UgP-|z0ViqYHI&S{Fkk^%lr2K zh}-x2?ETy17#eDY1Q>)E85IPHrI20Iz&v}K+=HUM`?t+YJhJPIbM}t!@Aq%L_?6vC z{UW0W3rmBG0|W6CvdIq)EW5}Zv+n!#YB$T}r8C>VCA|J>|NBotn%^Sk2^<^@oed1c zQ^*D%X!Kny{^Qrb%Dw3#KkCmP&wU~J?N8q8@AvJU=f_X4{9)d6&a;G=5Ses-EqjCD zy;RoQANgS(MYf26FXvWO!{+^`U;Ny``}W||D*34WK##oJ|NZage?1blb>E1z2sL(4 etU%>7T>imcUFGo4CF61fNXFCE&t;ucLK6UWb-LaF diff --git a/test/golden/goldens/mixed_inline.png b/test/golden/goldens/mixed_inline.png index 767bc53d373ca5b1d6f02eacf5693b2e71c9c912..de925a1dd1276b67775692c64e6eb309588633c4 100644 GIT binary patch literal 5529 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 SU)8ofAR$jzKbLh*2~7Y`GVenG literal 874 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCKjB~llH00_)&nV)VkgfK4j`!ENa+Cb8H-v8?bw+5g?IhF=vOzkFEp;aE*jo*ufIUu zZxJ&ggE1^cHugZT?q>FitI4(VjP3seP0lv^U704mciw^D7M*+!N(u}rOiaX6$R;zG zZ#yIRAhiE$<+(q9lJB>zyZPq%&yvOa`5*kZq}=G9eGh?d4qs>gE%${_`18h@ZvW*H zRMamrda$rGxHvEnPocOOlOvHg)Aqn_+iTA&pZ)^-;%a5u?p<>8|NYtAd$0dP*ZrH! z3?F7vV(Eip({3{FxefQutk}8jJKf&1|4^Yca?yQL(_249&nas9KbA)zDNk2Fmvv4F FO#lXI=R5!a diff --git a/test/golden/goldens/ordered_list.png b/test/golden/goldens/ordered_list.png index d0f9475e218f341387ea2900ade51ed0779c2a28..33a9914d7506d42e9eb76b2b30294158f0d7bdcb 100644 GIT binary patch literal 1432 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCzu;g4k_UHgx&owFik&<|IDnvrBc%h#XDkkK zcVbv~PUa;81FMs#i(^Q|oVRyui#}P2G(2qfDP!PeO`YL6d+UZ%Yg(sC1_fPck!3iy zHuhOWacX_tygnzV`ZLk0fr|fAi$8eIPT<(;YBwSU)s_7!LM8!;-lIxu($3NQq* zur!D;62p~bNeSI`zs!5({x+bmUmm;D_`4Vs$+*D*l{HcN5Ayr&{(D??qVL`BORg?c zF74k`9CQA+_mS|bKLNM@O5882>8bO7eDC~^_vtUwL?_(+{ruV5=>J<{4nO{S=iWa5 z?^4CSiS}DepV?lmus6MLd^$KvpP`{rh!mHDya_Zq^kv=urJyhdn#Oyj!v3BD!s!Rr z(7>t2tM|LGCs+|3!qDiL63Ge*>H19G;2Zq8y_L+S_xjB_5N*E*U$fV}{-MHl`}X&j_a2LZ818R+&;RWA zGoQ}9(!a_V-`>zVXfH{#WlMm!%58<3HN(2yy}dW(&JH z^>@GX1d|)6jt*fzXC$ra(-H1ua0p%f@Hiw$Cx5MdsifrfGXAFJ zy61mYA8Fgxt$15E&Cbs4{_{=m^;i9uTd>a2q3r$q+1vB}Ue2==?7v=I{oC$t_8HUG zzdCnk{4S|41?I%r^{FJ51fXKWh~vbnlKpR|1=Ry%|NN!ioy^~#^B}nsgSq0ZJ+GbR U#Z{NCKLyEoy85}Sb4q9e0IonA;{X5v literal 910 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCzu;g4k_UHgx&owFik&<|IDnvrBc%h#XDkkK zcVbv~PUa;81G9yvi(^Q|oVT|(PF(FE!f^1hdXq29lYA|v9HocSpHyD6t@@_2%U5P^ z>ijI7x;mh4%D}%}FTWn#v*!4Wzi_%&s_~1MCvb2u zbT%*$Pa&I+goL()o%av@6TV=K8yR;E631qUPXaH zg^9^QiC7B7p745lbH~tUN zpEt=V*Vo^do4#-J#pj7@HvcXut6z67{oT>&FGzDc$d}0A!_j5e*@31fyH(!We*Csi zdXL@t<<`~m<@Nu=-y8o?-RHutASl2f#K=fIg=~IFVdQ&MBb@0N_>a AF8}}l diff --git a/test/golden/goldens/rtl_arabic.png b/test/golden/goldens/rtl_arabic.png index 9e32c15324259dbed4486b266e04824f1fe984b0..782960847a274920b0ce0d162313976eaefcce05 100644 GIT binary patch literal 2017 zcmbVNdsLEX8voQ(HgmMIHkzhMwN7gnkFyo21!S44Wm;ZFW7I4aPo>Uy2_Y^zgR4cQ zlTMfM!gxy;FLWjyMG(iPEKwvwbQ6*GXsD1NAtETSAJ&|8W_Hinv+p0@_q@;hKF{}D ze$Vf_7IrGocD?g@003-*{(9^T0GJhn@=p*e@acJce-Zdvm+2oA0Ra~tl2{1t%`(pf z`U5S4TV?^kni6#ENJLKgoM`Cc)W~M*`I@17X{d}LoNL1$qz}TFl_=}-#p;een}uHb z(B0V!OPg=+O{?0IMGqbr%Oh{-$rq^waUJJ)4UT1&;mNF;%+|~uTm>en*zod`?JHa# zH`jddi~rw3qBk*Yawcn=JotK#YqQwwjgM!WT(r?#9<$??y-vR@y9$q#>tc<`)05fN zd2i*e@CnanUS)}CXpG-ycX@Hy(5<^9EQ%BYCBuydU1jLEOyX$wE3HG#um$k&elNpj ziDZ}E8|j-W$EPvqB);nJ8KVjv%uigp5dxgvXAe}TxC6(AkU(K|9$+6}Vdfq@&F)3r z>i_y;Mpjn}%2hfShVi4K4@0+-HYejl55S8u+9H#a0Q)n ze=#pUDzi4ot@`4xXi)$EtQ0PE? z(2%H{dA33u!I1GPKv)~bO`_r|QDo(vA9;h*OVAP@{QekTf21j2kiS0xIKXv3?hZ6j zK^T-a+1B2~vRVc#&DV65g|3LMkku3smnWO;uG@h_6+i05HX0PLqtEL(@y4K-kO#{l z@AxX|HZnh^WFYuf(K7+1tSh-~MTLbVzzgxT-D)u*Hl(*Z?GA%vteqf%sk{@2uL*M6 zi8vJZsFrnl=Ie)!{!2KBDR{AHS%MBYY@h|!#mVDW16->W)Zt|>3_NmBwz z_8R+e`w*yxYE}p!Alaj#ZWYTPN5$SCDV04612aX4hYJbIs=0>GmtIGRO`webz+?0O zsG9glF{}S3T_OPIVu^W(uNb$isiCU!MD0_1(iJ=-pn+7bf8%0l^y+# zY+OHC2ddt*klqiWBJ)l8)=C5fitl_G&OK2XKa&cN43vg;k-!AonC_ z{GQNBEo|zmg+s_2;DjZGe3Gknc>-CN3-%8vyxo@9>TQ?4ck6Fb`qlPaq|w5%XP*;v S$GzL8gP?#@$6EZ)ef2L1yvteu literal 1376 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCAK+jEl3H%2{XmMP*vT`50|;t3QaXTq#^NA% zCx&(BWL`2buyT32IEGZrd3*O@mrSS#+k?yg)ye8Iq=VgA&y3(>=^KWkc9OzvCew*c{^Yt%(M&0~u{Xg~jV!m%5)fjZn z2YzGtzkRi2xoK{hUG%q~`nK`f)pgJJzxfjorohmmA;91y!oi@(#nK?a%H+_&#HirX z$RJ>}^S49R{^fJ#o}X6x&-?uKQ}gcs)VH>uA_`PG`6hSv%5Pi6#SYw9 zEGzk4wRqv~59`kPegC$bQDXkx3Pz5bzdt*at<|y7XGxHw#ORyMSKi&*ZDsvd>HX$& zuFq#g?F;+%=O!pP)1POjp1)sZ*A267)7z|PvA*}K&{fps;0x2;pfFXxi$m9v_sch} z-@Ac9Vmc_0A@P9}1Q*&obC`=BA7jtAv8*V|^?0^*|GG7Dw!fbBTRzF&YG;38ndh5h zzu$Z~dbauBhuexX)!mofn|%Wp#8CYIIIe1A26`nh^(fyxb5)_*JX zenv%pzFt=s1&rZY_vgMibL!{Q<=fdbiUe((T zGW#x1|MYXt=dGKb-~T%Os&@T7$NPJuy)>7;e!o_(?)%eihwoYK{F@oR>%7;?`Z=Nd z!an`kI)DDP&s&ept1n_Lir5ujZ~6P-bKmu``+xp>z4U+jzs3JD9(Qh=xBlv{KU;TQ zs*Ui{oPKWo9}&xSUu)K0d!8PnmuM(`{CN5LX+d^cOKmFt{CIu8UG6$C5MO^bwqre% zvuDr0eUEqj+V&XeiLBbXW6K?8^nd;OJ@&u;hPOL_76W~8QBxpI`S-8dGMFZvl=%JU z&w-Q|`Misc(D$nUdv@~oc{w#3-`|h>|6@<}zK^dn);R))>{NrY?U%#r|S^xIQxxa6Z@BNeb`Rnhn&s)`u z>fb(J4)o5(=jEK;krDCX`&Z;%e;<7o;($kK8{V8{*bH*1JW_;1m=SBf?A?%6-XAk} zZrQ}|k?Yj$Ei-K% y`wk6lV3xo1-TBt_xCn-x(+CY1b|VQy^z3Kc`0e&mLuRp1kg%t#pUXO@geCy!`cZ=b diff --git a/test/golden/goldens/rtl_hebrew.png b/test/golden/goldens/rtl_hebrew.png index c37f27f8e132c442863d3c4702c6a1ebdb1a7e9b..971d1d573875cd23aa3cb133a740fac3e2679c15 100644 GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCui#(=Kh`KJD{$;DBx&(+*Lx;SAu3(!Eaz=8Ewuh#ypwO?a?-O~St4ZptTQj@Qi z^+o)@U&wqu^?a3W{p+7GTc79K&R_lQ&(}|(+)qBe`u1o|{J!Tl$-D2b4f4u-RlM!@ z-B)kFzErk4KIhZUtn>G)?84vvj6M(2eD+V07YD@_ zA4`J(E0aS96QhDlBZI&q2L_Hn1%{S0@6z}D-LzlwTKW9=wY`}({)_+Y52^ll?K9kx z{<-sG8+DGo`?!1SyN1cpeqdLweO`QZ-u|yO>u!CPx3=&0kGQ*N{-5%r_h$x!orL5c z)6bhNxL049X~rOvCuS|c(B}`dJ!q?gS53RlPhDZ80DT~?T{7h)IGBQkW@Q1Gq=m!k}1}6~?21PD}7nl4v zU-3J&-gwP>yZuqpmn-Jok>@brKFXb9Sk`}kZRt`!KgI>Sl;&?x`aWs*X&EF(K$s7n zh+5tKuuuNl?AMx|XFq?Kbm{QEGrt)vwkfd|RhCv4Uemw7JOvshi;n;$?8Lno1%m$A zO_*OMHgPT&!~V&#s1AaKx#mCPdv!PO^Ss`De*Lv$Ci9kW{36Y`Vqpa|gx6g;smTx$ z{zN!z`wR8=+DeibPGd-0df{c;_rDwN#rS*X%xj-MMebYbJ^lk5MKvd!UVXcJbKOrn z)y;R`hvl#LmsbT!eB2qp)iB}F_r^b4Hg~)UVfZok4K(i2!p-C3{Ey#G|4WOwUt71; zY}rSf-lg|B9C$GUZI`QkWxU`?Os5%WPxUw7^S=G-GppyE;24~;%cQ)qD ze)!kB&#Si@_CC*?@_+i7`#>#ke`Y@~-fH&$4Hq;;u>X1dcjfcqnfrm(-THj}-1Ds5 ze~~qZUWxsE68G1-_!YA2qvq9%r6*3cn#|U4_-qn8(}9ngz!bPj%%k&tf$Dx&Cd`0A bh@81yzq|94pRPudGs`v8<-T14v|KI#v_3mf-`S8;7_ox1^ zQD@Q}%WK(a_6wwx}xV%kQxJcETTSTqKjqJJfoiAi9qB94N*?I0jR5ue9IW+xp$+TVlO* z!|$EF;teN0e0Nx9#{IsPVa-9@9%-A;TzHQ8fn<&X7Gn>DYzvgH`Yv|w^NseuxeV`~ zo5e9G9lp=J^OnvYai$F*w;%@!QW#0r@g1?`Z%7Av2nz_lxr=?py?fP2k(2swCnzk- zpRZ1vdp~y%gG<|f_DgS9MXsOq>*Y_a&*$d?!+5`Syy@*fX5cVw=wM=0SOgDZFn7}( z%L%XE&zV1Oz0vcd}c<4O=n^w819@#1OpaF!G-!i@;@_bx<0vhSvg3| M)78&qol`;+05<3O6aWAK diff --git a/test/golden/goldens/rtl_mixed.png b/test/golden/goldens/rtl_mixed.png index ef08cde3ee0f86032a3cc5755c62549c59242422..d8ed84db50c97bc158bed8749d3ca3aec007aa19 100644 GIT binary patch literal 1264 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCui#(~?Mnx*;ddD{Dq=Eqxaz1Y0Hcuh?gtGXc2RPsPgWUOwTdC%;(R!FD-#w)?ewgIJ4^Ywa3rCMQi2!72!yU zjs0JD?`^pKCH5QozYjmxe(~#Jb;|UEzbac;9r}PqEDceJwg16qUOd&*ruA$5udkP< zRdXQ1!y+KO`}#MgSy2B?w>|c)u%>H=|FhfY_J4WZf9&)1^Zcj7v?i>6J-d5z;@7G@ zg?!)l{oYYiSyyp7W&fc+p~d_EUHcqy`r`*Qc4`g8hR_||#*OaBF^ zhb#Qu_xtVrxYu=$-|sy&w~-uk)bmYi=^EQYh98ycA9-0C&MyaA zmKmjRyn?U%O!`8kz+i}vDAb<3JMm7=^_yQzzB;?O-1oQsdH%e+OOE{U!0V(pZUh{+WN0QSFT(m|C#8d6}4xFcn~;A&5VvJy<2tXPMvq_T6vwQ zmB0j0Z~y+=YWbJUZ)$&Rx%Tz&_Y-=}Uz=S&b8v&Ro7YN*U3DLLnkSvIeey5+ycEJO cn5;8r=O5U1xMi1Zuo_6x)78&qol`;+0L&30Z~y=R literal 744 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCui#(cLIDyUkpXdpo_5ajrSsF~~MpY`p!A;qvAQap#Y( zjd^~5msR`M{k!W<{ka}zzt#5tw_m5GzhF>VZr%HR_olk=tk>n&{b&Ba^?LcX=lA3O z2j2rb7>nh8-(^pH`9Ejv{aE|df7$Ej-a_qTB z?)-Tt&wSA_{9T`OZc_3gko`Cr2SD#J5deUe zqc_~$_gs;X+d>GAHf){e7Y%ir&*mDhq0M_Fn1_UX%bQSy*1&?GF@Gup@o@Gl)T_L(yn7_#y7shK7cmmAu1!CPIp_bWdM0QwgcC+wShev8g4oN zG`qf8*`;7!QpFv!RXyuAhoMYiv1v9JAGRxT@Ov_}sN;$yiNM({>u^;WT&}N|Ds74; zCcDiqe1v5v*s-{Y=U$%JR%DKaJU9n0AH|^cyGbcxkb|RRE<`n1;Tf%*&UVFD6gyiM zsB9@|FZiX-`HDVqdefZi3$$vEC7-HEyG%h{h=tm-9C?tUGkD(UBN{Mo$HsKPQXmrH9Oo4Z`I zWS^!v&2`;9kjPyolgVY$HJl7)LYp&mQSYEERdpJRT`HLdiT9FF=D||EJ@XtEZ-rBO zyS4BPNxP;Nc(7#?fY8*;&TrB~AYY*C&zl+VfF{>Q5t17kInJH+;pF`ATH$$B0rRn)(RCas< zX@pwC2^2DYZwcezBRxI6=K})&i)3$hTk}Q`b~GPz~FzEUOdw_DV-t&3@wIz{?MeThh=^4Na|pq z6nX5z7mUZTMU*0^bYz_tCTboNW#~eBwk5AIM$atN)sg4VJu$$_luC5@v2vzg#HaS65oS=LaIa&F zp=>BQfmA0!_D3YP;f7<5XvvbPG#x>ZnFf2kOO(%V^Kjk?>_lif?j)O3T@4#6Ly=-yw-{y13kX|MA=pMSzSM|KK8X;e z1HBzt_1t|CxqI|psCrPG3p$Ku&XBJl2zSHY=9I?v6E*A2N2Y7q;vrJjKx9j zP7LeL$-HD>U@i1?aSW-L^Y-q>?ATP9;}5@kC~WlXS|Q4681AAmaY67)iT@0OEZRXX zF|A^EnkFYd7u(f!W2qls(+<~yjUs|kz79|JEv#?aw#>O$rbP7cciWRY&1>iV{eExy z6lf!M;QTU5e8tvphjUmP9-GYcnI*Ns;E39}IhMvpjQY1eefo5neahO> z-(Ox{_Se4 z3Z|Xnxij5MGxhP-&Ay-4_Sq=>{>^{7TE6bimSfV=ub)1*-rr|t-!eNsM*eUA-=EXt zr>@`sQ*Yk<*4H;)ZT7d<6u(!Q{U-N}ZF;8JJ8%2HA95q3qH>}Zx31s7(7}Pv3)m&y z|5Yn^dv`y5CVqYPHHqKt(QUsRS8lBRai#9Y{4M?RkLQlQbCWikT#?#bFzs7eer(@! z_6M98F^y^$S~7r0Cdh2PR`

    z-w)3wbk6bzP#pzAwH1%Bx&;{`+maE*<~8^!@s`Z}07r zIe)%m>(|fX-uLf&Ja6C8r~5pi!!+%oPH!zQ->JpD)z;hJZ_7CtIjeWG$+y~*!lWk} zCZ+=xS!@5^s9rE7>h-r0tGhDQ@@MSRI}d+ifn*m{4`T-6#C4^N?+-D}@Sl8g$xcRJ zmK3uJMmgugX%;&1iHykk4!c3z$3H4&r=O42jV;%l``&y1)v52i?C$55F71EreLp_? z=6``~nJMnl-^=pu=iWSY>(|fjrSJ2Xd{%#GHLqp!mhGP(?|832Ya) k4C#X)7_b+0aMAxy^S8%dnK9wW-fJK+Pgg&ebxsLQ01sd$4gdfE diff --git a/test/golden/goldens/table_spans.png b/test/golden/goldens/table_spans.png index 7965e85df941b24d68d1acfc45544a53073dc39c..dd1c7c0d8e841adc1bfe1e4025bc44a5fc814875 100644 GIT binary patch literal 1398 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJC&*fkPlC#3}mH{c2VkgfK4j`!ENa+Cb8H+mq;MauGbqm8!ZPXmnKO4~=P#dE z{OoCw(f$JAcfCM6u>-ZGT6Y($d!1&qn8CpNZ{7YCjNFqY7F~bsn-D4d@7yVR`n31kwfApzJze_eS4YR%vuh)=KA$&untfsAs))$AnwR&# zK5X6_5gF-s{=%I3ZHR9z33;*1>xag~(;Q4vE#}*$yyvX+3udR1@pRc@f zg?ZE7y~6Kn3-)F7m*20nUKg`BZo|K|JBq#@UnF^J+wJuSA3E>-@#nJtqr;!&_kJ+8 zulXH%vH!gJ{aX3>nxCardy5TbW`B6?AD&cL7=KZ&Uyi@KUfk<#TlUWDJ6l;Dx*8ZJ zC@C;3;oxAAAZ=Hx4pWswDjwlZoB8LLAxK@%(eUK7Ra3T$?cHmo`XGib4{d<9y{hHV_#N~ zb-(b*R_EZ`vu{R4Mt<9Ez<%ct6MA@~1wV*8^Tg&2YQ|g)o3Fh8-Yp&A;?U6{!Nc}< z*Sgo+o-e3vKKLLf_WJc(^O|24b8};hLL?)nv_{sPym&c(UtF)d_sy@r7j51v{P^gb zUxg*xtXW^XKdwE$d+yvt*``wFK!(;{_m`$Gj{I2v>zaQ5rq}9^W6IxN`E_!V?6v&0 zcRP2O%(~;V<#6N-c4JPDbdy~&g3C9(cE9?g=<1Q*FW>9O><@T*r&Fzq@3&K^=-a?5 z-dQi|#02HjulpaGDE$3f_}N2e57}1nzdttlq_3;=oYtQY#KcI5A!u}-TEp~dPs+y~ zoy$J_?!IOodh^f!tnX4Gk1u6Mta(+^oj&7;+?Ml^Gx&`w$&G0 zf3r0oyt!-L`mMI?m!A{%40kU7f&V9WzPq*Z>tt2yHTR=-3m0aZ?3|g=zS)4ErDdjN z(W86+Rz>cJ+~NNI;oS~X5=*-%+&h`RN0{^S;zvsDwib*`c=d68?=ESmDmdv+7syto& KT-G@yGywo7NHdWD literal 1200 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJC&*fkPlC#3}mH{c2VkgfK4j`!ENa+Cb8Hoo@MK`yctUwHu(5n{*razp`w`K?6Yk%+Dyv|3Ie`;`*!Nu zwQD!L6J27j$Id(&nqZ{FMMJU>@|BfGEMe_w3f-ooN7A6F*;ja|L|%&Dj6 z?KFXsUv8YQ`F&M;6Xc$y6@9F7x%<+g$u51uK#g%G4s-A^?(1~IByY<_4j@Arzy-! zcdgrPHUGT6-}1}ew{PEGF1MSH-D~ZUzhC~n{9a|6ps6mzSJ))E`t5@U3*NkatNUel z-RraFVrJ&2x9;5eu>7Iq15q(-p^fSoqZ4rvhjmyRQnO|)D`&0}6!6kqntOZMwW#B9 z?2of=f6H0>_0)~m2_N?yVnUBLRD(cq%BTOh|BZ}&?M@#x`Q?vyUy7YE<>d08KTfuK zzI^m?>1(_9oBt_3dr{4MD&^C`cW3AC?>x?*^8Comxaj+D?(MRBch8pR*xn8QY#JfS z0@Hk?G=m}8v-sMrZ2$Usyt%i{wqCRPW+f4Sds}+;+VEp>?A^~f5vdJBCvsApyifQB zH+O=qsqf|8jC(maj;3|}=|3wnU!JIVZa*H!aDMt+;qwBHnX%?+xS-gx_@Ap? U?3NWOse{BkUHx3vIVCg!02?63{r~^~ diff --git a/test/golden/goldens/text_formatting.png b/test/golden/goldens/text_formatting.png index fbafe7fdb37e9ba7d849d896aadd1626b799eb9d..d0079d5cea8a1b79377840729752e48cf2bd0756 100644 GIT binary patch literal 4478 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 av;H&PdvCM!pp{o8NYc~Q&t;ucLK6U@^jE$B literal 1110 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKN1{`cak(rCtm4FmWv6E*A2N2Y7q;vrJjKx9j zP7LeL$-D&0F?hN-hE&XXduLkWfJqb3H@~z0nR9+KJ41ml6O)6I z0)q-Zs{WkZhug7#x36HR>-z7&;K9Pu;NpPdt_Q8Uo9zp}Y+u}0JA2p3{K(kbZ=N&G zI6nIYeuJ@DiefG%$7lN-+XpwV-TrG>^zX*@mE~r;+P|Cqu55Y#!={>n;kpU|ub|tA zVu5~+W%Ys0Yq$SSe)4Bye(?L868`HzvzyXCsaY{}HZV-!;2?oQHhRy7)NkzTU Date: Thu, 14 May 2026 12:09:58 +0700 Subject: [PATCH 3/7] feat(core): optimize text selection overlay performance and add list-style-type CSS support, update demos --- README.md | 56 +++++---- doc/ROADMAP.md | 44 +++---- example/lib/aesthetic_demo.dart | 1 + example/lib/css_properties_demo.dart | 29 +++++ .../virtualized_selection_overlay.dart | 51 ++------- .../lib/src/core/render_hyper_box.dart | 55 +++++++++ .../lib/src/core/render_hyper_box_layout.dart | 107 +++++++++++++++--- .../lib/src/core/render_hyper_box_paint.dart | 5 +- .../lib/src/model/computed_style.dart | 25 ++++ .../lib/src/style/resolver.dart | 79 +++++++++++++ .../src/widgets/hyper_selection_overlay.dart | 42 +++---- test/style/css_bug_fixes_test.dart | 90 +++++++++++++++ 12 files changed, 446 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 0ffaa76..1691386 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # HyperRender -### Fast, robust HTML rendering for Flutter. +### The only Flutter HTML renderer with CSS float layout. [![pub.dev](https://img.shields.io/pub/v/hyper_render.svg?label=pub.dev&color=0175C2)](https://pub.dev/packages/hyper_render) [![pub points](https://img.shields.io/pub/points/hyper_render?label=pub%20points&color=00b4ab)](https://pub.dev/packages/hyper_render/score) @@ -13,7 +13,7 @@ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Flutter](https://img.shields.io/badge/Flutter-3.10+-54C5F8.svg?logo=flutter)](https://flutter.dev) -**CSS float ยท crash-free selection ยท CJK/Furigana ยท `@keyframes` ยท 80%+ Test Coverage ยท XSS-safe** +**CSS float ยท crash-free selection ยท CJK/Furigana ยท `@keyframes` ยท 1 646 tests ยท XSS-safe ยท Zero Gradle config** [**Quick Start**](#-quick-start) ยท [**Why Switch?**](#๏ธ-why-switch-the-architecture-argument) ยท [**API**](#-api-reference) ยท [**Packages**](#-packages) @@ -26,7 +26,7 @@ | CSS Float Layout | Ruby / Furigana | Crash-Free Selection | |:---:|:---:|:---:| | ![CSS Float Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/float_demo.gif) | ![Ruby Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/ruby_demo.gif) | ![Selection Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/selection_demo.gif) | -| Text wraps around floated images โ€” no other Flutter HTML renderer does this | Furigana centered above base glyphs, full Kinsoku line-breaking | Select across headings, paragraphs, tables โ€” tested to 100 000 chars | +| Text wraps around floated images โ€” **no other Flutter HTML renderer does this** | Furigana centered above base glyphs, full Kinsoku line-breaking | Select across headings, paragraphs, tables โ€” tested to 100 000 chars | | Advanced Tables | Head-to-Head | Virtualized Mode | |:---:|:---:|:---:| @@ -39,7 +39,7 @@ ```yaml dependencies: - hyper_render: ^1.3.0 + hyper_render: ^1.3.1 ``` ```dart @@ -51,7 +51,7 @@ HyperViewer( ) ``` -Zero configuration. XSS sanitization is **on by default**. +Zero configuration. XSS sanitization is **on by default**. No Gradle setup required. --- @@ -59,10 +59,10 @@ Zero configuration. XSS sanitization is **on by default**. Most Flutter HTML libraries map each HTML tag to a Flutter widget. A 3 000-word article becomes **500+ nested widgets** โ€” and some layout primitives simply cannot be expressed that way: -> **CSS `float` is not possible in a widget tree.** -> Wrapping text around a floated image requires every fragment's coordinates before adjacent text can be composed. That geometry only exists when a single `RenderObject` owns the entire layout. +> **CSS `float` is architecturally impossible in a widget tree.** +> Wrapping text around a floated image requires every fragment's coordinates before adjacent text can be composed. That geometry only exists when a single `RenderObject` owns the entire layout pass. -HyperRender renders the whole document inside **one custom `RenderObject`**. Float, crash-free selection, and sub-millisecond binary-search hit-testing all follow from that single design decision. +HyperRender renders the whole document inside **one custom `RenderObject`**. CSS float, crash-free selection, O(log N) binary-search hit-testing, and `@keyframes` animations all follow directly from that single architectural decision. ### Feature Matrix @@ -70,13 +70,18 @@ HyperRender renders the whole document inside **one custom `RenderObject`**. Flo |---|:---:|:---:|:---:| | `float: left / right` | โŒ | โŒ | โœ… | | Text selection โ€” large docs | โŒ Crashes | โŒ Crashes | โœ… Crash-free | -| Ruby / Furigana | โŒ Raw text | โŒ Raw text | โœ… | -| `

    ` / `` | โŒ | โŒ | โœ… Interactive | +| Ruby / Furigana + Kinsoku | โŒ Raw text | โŒ Raw text | โœ… | +| RTL / BiDi (Arabic, Hebrew) | โš ๏ธ | โš ๏ธ | โœ… | | CSS Variables `var()` | โŒ | โŒ | โœ… | -| CSS `@keyframes` | โŒ | โŒ | โœ… | +| CSS `@keyframes` animation | โŒ | โŒ | โœ… | | Flexbox / Grid | โš ๏ธ Partial | โš ๏ธ Partial | โœ… Full | -| Box shadow ยท `filter` | โŒ | โŒ | โœ… | -| SVG `` | โš ๏ธ | โš ๏ธ | โœ… | +| `box-shadow` ยท `filter` | โŒ | โŒ | โœ… | +| `list-style-type` (all 11 values) | โš ๏ธ disc only | โš ๏ธ disc only | โœ… | +| `
    ` / `` | โŒ | โŒ | โœ… Interactive | +| Quill Delta input | โŒ | โŒ | โœ… | +| Markdown input | โŒ | โŒ | โœ… GFM | +| Modular packages | โŒ monolith | โŒ monolith | โœ… opt-in add-ons | +| Zero Gradle config | โœ… | โœ… | โœ… | ### Benchmarks @@ -353,10 +358,11 @@ HTML / Markdown / Quill Delta Kinsoku ยท O(log N) binary-search selection ``` -- **Single RenderObject** โ€” float layout and crash-free selection require one shared coordinate system +- **Single RenderObject** โ€” float layout and crash-free selection require one shared coordinate system; a widget tree cannot provide this - **O(1) CSS rule lookup** โ€” rules indexed by tag / class / ID; constant time regardless of stylesheet size -- **O(log N) hit-testing** โ€” `_lineStartOffsets[]` precomputed at layout time; each touch is a binary search -- **RepaintBoundary per chunk** โ€” unmodified chunks are composited, not repainted +- **O(log N) hit-testing** โ€” `_lineStartOffsets[]` precomputed at layout time; each touch is a binary search, not a linear scan +- **RepaintBoundary per chunk** โ€” unmodified chunks are composited, not repainted; incremental layout caches unchanged sections by content hash +- **1 646 passing tests** โ€” unit, widget, integration, fuzz (43 cases), and golden pixel tests across 3 OS platforms --- @@ -389,21 +395,26 @@ HTML / Markdown / Quill Delta | Package | pub.dev | Description | |---------|---------|-------------| | [`hyper_render`](https://pub.dev/packages/hyper_render) | [![pub](https://img.shields.io/pub/v/hyper_render.svg)](https://pub.dev/packages/hyper_render) | Convenience wrapper โ€” HTML, Markdown, Delta, syntax highlight | -| [`hyper_render_core`](https://pub.dev/packages/hyper_render_core) | [![pub](https://img.shields.io/pub/v/hyper_render_core.svg)](https://pub.dev/packages/hyper_render_core) | Core engine โ€” UDT model, CSS resolver, RenderObject | +| [`hyper_render_core`](https://pub.dev/packages/hyper_render_core) | [![pub](https://img.shields.io/pub/v/hyper_render_core.svg)](https://pub.dev/packages/hyper_render_core) | Core engine โ€” UDT model, CSS resolver, RenderObject; zero native deps | | [`hyper_render_html`](https://pub.dev/packages/hyper_render_html) | [![pub](https://img.shields.io/pub/v/hyper_render_html.svg)](https://pub.dev/packages/hyper_render_html) | HTML + CSS parser | | [`hyper_render_markdown`](https://pub.dev/packages/hyper_render_markdown) | [![pub](https://img.shields.io/pub/v/hyper_render_markdown.svg)](https://pub.dev/packages/hyper_render_markdown) | Markdown adapter (GFM) | | [`hyper_render_highlight`](https://pub.dev/packages/hyper_render_highlight) | [![pub](https://img.shields.io/pub/v/hyper_render_highlight.svg)](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
    ` blocks |
    -| [`hyper_render_devtools`](https://pub.dev/packages/hyper_render_devtools) | [![pub](https://img.shields.io/pub/v/hyper_render_devtools.svg)](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools extension โ€” UDT inspector, computed styles |
    +| [`hyper_render_devtools`](https://pub.dev/packages/hyper_render_devtools) | [![pub](https://img.shields.io/pub/v/hyper_render_devtools.svg)](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools extension โ€” UDT inspector, computed styles, float visualizer |
     
     ### Optional add-ons
     
    -These packages have native dependencies and are **not bundled** by default. Install only if you need the feature.
    +These packages bring native dependencies and are **not bundled** by default. Install only what you need.
    +
    +| Package | pub.dev | Description |
    +|---------|---------|-------------|
    +| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Native image copy / share via `super_clipboard` |
    +| [`hyper_render_math`](https://pub.dev/packages/hyper_render_math) | [![pub](https://img.shields.io/pub/v/hyper_render_math.svg)](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML via `flutter_math_fork` |
     
     #### `hyper_render_clipboard` โ€” Native image copy / share
     
     ```yaml
     dependencies:
    -  hyper_render_clipboard: ^1.3.0
    +  hyper_render_clipboard: ^1.3.1
     ```
     
     ```dart
    @@ -433,7 +444,7 @@ HyperViewer(
     
     ```yaml
     dependencies:
    -  hyper_render_math: ^1.3.0
    +  hyper_render_math: ^1.3.1
     ```
     
     ```dart
    @@ -443,9 +454,6 @@ final registry = HyperPluginRegistry()..register(const MathPlugin());
     HyperViewer(html: html, pluginRegistry: registry)
     ```
     
    -| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Native image copy / share via `super_clipboard` |
    -| [`hyper_render_math`](https://pub.dev/packages/hyper_render_math) | [![pub](https://img.shields.io/pub/v/hyper_render_math.svg)](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML via `flutter_math_fork` |
    -
     ---
     
     ## Contributing
    diff --git a/doc/ROADMAP.md b/doc/ROADMAP.md
    index 1b15966..d5140d7 100644
    --- a/doc/ROADMAP.md
    +++ b/doc/ROADMAP.md
    @@ -13,7 +13,7 @@ For detailed CSS property tracking, see [`internal/CSS_SUPPORT_ROADMAP.md`](inte
     
     - Single `RenderObject` pipeline (Parse โ†’ Style โ†’ Layout โ†’ Paint)
     - Float layout algorithm (`float: left/right`, `clear`) โ€” unique advantage over FWFH
    -- Isolate-based HTML parsing (non-blocking UI thread)
    +- Async microtask-based HTML parsing (non-blocking UI thread; uses `Future.microtask` instead of a real isolate so `FakeAsync` works in widget tests)
     - `ListView.builder` virtualization (low RAM on large documents)
     - Full Flexbox support (90% coverage: direction, wrap, gap, align, grow/shrink/basis)
     - CSS Variables `var()`, `transition`, `animation-*` parsing
    @@ -79,12 +79,12 @@ jarring for large images. A workaround is to increase `chunkSize` so fewer split
     occur near floats.
     
     Scope:
    -- [ ] Add `FloatCarryover` data class to `render_hyper_box_types.dart`
    -- [ ] Add `danglingFloats` getter to `RenderHyperBox`
    -- [ ] Add `initialFloats` parameter to `RenderHyperBox` / `HyperRenderWidget`
    -- [ ] Seed initial floats in `_performLineLayout`
    -- [ ] Wire `FloatCarryover` callbacks through `VirtualizedChunk` โ†’ `HyperViewer`
    -- [ ] Add offset rendering support in `_paintFloatImages` for image floats
    +- [x] Add `FloatCarryover` data class to `render_hyper_box_types.dart`
    +- [x] Add `danglingFloats` getter to `RenderHyperBox`
    +- [x] Add `initialFloats` parameter to `RenderHyperBox` / `HyperRenderWidget`
    +- [x] Seed initial floats in `_performLineLayout`
    +- [x] Wire `FloatCarryover` callbacks through `VirtualizedChunk` โ†’ `HyperViewer`
    +- [ ] Add offset rendering support in `_paintFloatImages` for image floats (imagePixelOffset not yet read by painter)
     - [ ] Integration test: tall float at chunk boundary shows no wasted space
     
     ---
    @@ -98,22 +98,8 @@ Scope:
     **Source**: Expert review recommendation
     **Priority**: High โ€” directly impacts stability on low-end devices (2 GB RAM)
     
    -The image cache is currently tuned manually. `WidgetsBindingObserver` should be
    -integrated so HyperRender automatically evicts caches when the OS signals memory pressure.
    -
    -```dart
    -class HyperRenderController with WidgetsBindingObserver {
    -  @override
    -  void didHaveMemoryPressure() {
    -    imageCache.evictAll();        // Flutter image cache
    -    _internalSpanCache.clear();   // HyperRender internal span cache
    -    super.didHaveMemoryPressure();
    -  }
    -}
    -```
    -
     Scope:
    -- [ ] Implement `WidgetsBindingObserver` in `HyperRenderController`
    +- [x] Implement `WidgetsBindingObserver` in `HyperViewer` โ€” `didHaveMemoryPressure` clears TextPainter cache, `LazyImageQueue.clearPending()`, and `PaintingBinding.imageCache.clear()`
     - [ ] Expose `onMemoryPressure` callback for host-app customization
     - [ ] Debug-mode metrics: eviction count, bytes freed
     - [ ] Smoke test on a 2 GB RAM device
    @@ -122,12 +108,14 @@ Scope:
     
     Properties deferred from Phase 3 in [`internal/CSS_SUPPORT_ROADMAP.md`](internal/CSS_SUPPORT_ROADMAP.md):
     
    -- [ ] `text-shadow` โ€” high visual impact, 1-day effort
    -- [ ] `text-overflow: ellipsis` โ€” extremely common, 4-hour effort
    -- [ ] `box-shadow` โ€” design system compatibility
    -- [ ] `list-style-type`, `list-style-position` โ€” better `
      ` / `
        ` rendering -- [ ] `word-break`, `overflow-wrap` โ€” CJK and long-URL handling -- [ ] `background-repeat`, `background-position`, `background-size` +- [x] `text-shadow` โ€” parsed + applied to `TextStyle.shadows` in `ComputedStyle` +- [x] `text-overflow: ellipsis` โ€” parsed + executed in `render_hyper_box_fragments.dart` +- [x] `box-shadow` โ€” parsed + applied in `render_hyper_box_paint.dart` +- [x] `word-break`, `overflow-wrap` โ€” parsed + executed in `render_hyper_box_layout.dart` (L1339โ€“1375) +- [ ] `list-style-type`, `list-style-position` โ€” not yet in resolver or painter +- [x] `background-repeat` โ€” parsed + mapped to `ImageRepeat` in `paintImage()` +- [x] `background-position` โ€” parsed + mapped to `Alignment` in `paintImage()` (keyword values: top/center/bottom/left/right and combinations) +- [x] `background-size` โ€” parsed and applied --- diff --git a/example/lib/aesthetic_demo.dart b/example/lib/aesthetic_demo.dart index 283f0da..d253924 100644 --- a/example/lib/aesthetic_demo.dart +++ b/example/lib/aesthetic_demo.dart @@ -79,6 +79,7 @@ class AestheticDemo extends StatelessWidget { _buildFeature('โœจ CSS filters and backdrop-filter (blur)'), _buildFeature( 'โœจ NEW: word-break and background-size (cover/contain)'), + _buildFeature('โœจ NEW: list-style and background (repeat/position)'), _buildFeature('โœจ NEW: Advanced borders (dashed, dotted, double)'), _buildFeature( 'โœจ NEW: Professional truncation (text-overflow: ellipsis)'), diff --git a/example/lib/css_properties_demo.dart b/example/lib/css_properties_demo.dart index 4f5bedf..0389906 100644 --- a/example/lib/css_properties_demo.dart +++ b/example/lib/css_properties_demo.dart @@ -24,6 +24,35 @@ class CssPropertiesDemo extends StatelessWidget { // NEW PROPERTIES _buildSection('๐ŸŽ‰ NEW CSS Properties'), + _buildPropertyCard( + title: 'list-style-type & position', + description: 'Advanced list markers (roman, alpha) and positioning', + html: ''' +
          +
        • First Roman
        • +
        • Second Roman
        • +
        +
          +
        1. First Alpha
        2. +
        3. Second Alpha
        4. +
        +
          +
        • No markers here
        • +
        + ''', + ), + _buildPropertyCard( + title: 'background-repeat & position', + description: 'Advanced background image rendering', + html: ''' +
        +
        repeat-x + center
        +
        +
        +
        no-repeat + right bottom
        +
        + ''', + ), _buildPropertyCard( title: 'text-shadow', description: 'Drop shadow on text (multiple shadows supported)', diff --git a/lib/src/widgets/virtualized_selection_overlay.dart b/lib/src/widgets/virtualized_selection_overlay.dart index 0501e15..3a73cce 100644 --- a/lib/src/widgets/virtualized_selection_overlay.dart +++ b/lib/src/widgets/virtualized_selection_overlay.dart @@ -356,13 +356,14 @@ class _VirtualizedSelectionOverlayState final localPosition = scrollableBox.globalToLocal(globalPosition); final size = scrollableBox.size; - const threshold = 50.0; + const threshold = 60.0; + const maxStep = 20.0; double dy = 0.0; if (localPosition.dy < threshold) { - dy = -15.0; + dy = -maxStep * (1.0 - localPosition.dy / threshold); } else if (localPosition.dy > size.height - threshold) { - dy = 15.0; + dy = maxStep * (1.0 - (size.height - localPosition.dy) / threshold); } if (dy != 0.0) { @@ -415,7 +416,7 @@ class _VirtualizedSelectionOverlayState }, child: CustomPaint( size: const Size(22, 22), - painter: _TeardropHandlePainter( + painter: HyperTeardropHandlePainter( color: widget.handleColor, isStart: isStart, ), @@ -522,43 +523,5 @@ class _MenuButton extends StatelessWidget { } } -/// Teardrop handle painter (mirrors the one in hyper_selection_overlay.dart). -class _TeardropHandlePainter extends CustomPainter { - const _TeardropHandlePainter({required this.color, required this.isStart}); - final Color color; - final bool isStart; - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color - ..style = PaintingStyle.fill; - final shadow = Paint() - ..color = const Color(0x33000000) - ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2); - final path = Path(); - final w = size.width; - final h = size.height; - - if (isStart) { - path.moveTo(w / 2, 0); - path.quadraticBezierTo(w, h * 0.4, w * 0.85, h * 0.7); - path.arcToPoint(Offset(w * 0.15, h * 0.7), - radius: Radius.circular(w * 0.35), clockwise: false); - path.quadraticBezierTo(0, h * 0.4, w / 2, 0); - } else { - path.moveTo(w / 2, h); - path.quadraticBezierTo(w, h * 0.6, w * 0.85, h * 0.3); - path.arcToPoint(Offset(w * 0.15, h * 0.3), - radius: Radius.circular(w * 0.35), clockwise: true); - path.quadraticBezierTo(0, h * 0.6, w / 2, h); - } - - canvas.drawPath(path, shadow); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(_TeardropHandlePainter old) => - old.color != color || old.isStart != isStart; -} +// HyperTeardropHandlePainter is defined in hyper_selection_overlay.dart +// and exported from hyper_render_core โ€” no local copy needed. diff --git a/packages/hyper_render_core/lib/src/core/render_hyper_box.dart b/packages/hyper_render_core/lib/src/core/render_hyper_box.dart index ab26903..fe79b6b 100644 --- a/packages/hyper_render_core/lib/src/core/render_hyper_box.dart +++ b/packages/hyper_render_core/lib/src/core/render_hyper_box.dart @@ -1576,4 +1576,59 @@ class RenderHyperBox extends RenderBox return BoxFit.cover; } } + + ImageRepeat _getImageRepeat(String? cssValue) { + switch (cssValue?.toLowerCase().trim()) { + case 'repeat-x': + return ImageRepeat.repeatX; + case 'repeat-y': + return ImageRepeat.repeatY; + case 'no-repeat': + return ImageRepeat.noRepeat; + case 'repeat': + case 'space': + case 'round': + default: + return ImageRepeat.repeat; + } + } + + Alignment _getBackgroundAlignment(String? cssValue) { + if (cssValue == null) return Alignment.topLeft; + final v = cssValue.toLowerCase().trim(); + switch (v) { + case 'center': + return Alignment.center; + case 'top': + case 'top center': + case 'center top': + return Alignment.topCenter; + case 'bottom': + case 'bottom center': + case 'center bottom': + return Alignment.bottomCenter; + case 'left': + case 'left center': + case 'center left': + return Alignment.centerLeft; + case 'right': + case 'right center': + case 'center right': + return Alignment.centerRight; + case 'top left': + case 'left top': + return Alignment.topLeft; + case 'top right': + case 'right top': + return Alignment.topRight; + case 'bottom left': + case 'left bottom': + return Alignment.bottomLeft; + case 'bottom right': + case 'right bottom': + return Alignment.bottomRight; + default: + return Alignment.topLeft; + } + } } diff --git a/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart b/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart index 7e5cee4..b694c29 100644 --- a/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart +++ b/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart @@ -198,24 +198,30 @@ extension _RenderHyperBoxLayout on RenderHyperBox { final parentTag = parentBlock.tagName?.toLowerCase(); final isOrdered = parentTag == 'ol'; - // Get or calculate list item index - int index = 1; - if (isOrdered) { - // Count previous li siblings - index = (_listItemIndices[parentBlock] ?? 0) + 1; - _listItemIndices[parentBlock] = index; - } + // Resolve effective list-style-type: li overrides parent, then parent, + // then default (disc for ul, decimal for ol). + final effectiveType = style.listStyleType ?? + parentBlock.style.listStyleType ?? + (isOrdered ? 'decimal' : 'disc'); + + // list-style-type: none โ†’ suppress marker entirely. + if (effectiveType != 'none') { + int index = 1; + if (isOrdered) { + index = (_listItemIndices[parentBlock] ?? 0) + 1; + _listItemIndices[parentBlock] = index; + } - // Create marker text - final marker = isOrdered ? '$index. ' : 'โ€ข '; + final marker = _buildListMarker(effectiveType, isOrdered, index); - _fragments.add(_ListMarkerFragment( - sourceNode: node, - style: style, - marker: marker, - isOrdered: isOrdered, - index: index, - )); + _fragments.add(_ListMarkerFragment( + sourceNode: node, + style: style, + marker: marker, + isOrdered: isOrdered, + index: index, + )); + } } if (style.float != HyperFloat.none) { @@ -1576,6 +1582,75 @@ extension _RenderHyperBoxLayout on RenderHyperBox { /// Recursively extracts plain text content from a [UDTNode] subtree. /// Used by heading-anchor collection to provide human-readable TOC labels. + /// Returns the marker string for a list item given its CSS list-style-type. + String _buildListMarker(String type, bool isOrdered, int index) { + switch (type) { + case 'disc': + return 'โ€ข '; + case 'circle': + return 'โ—ฆ '; + case 'square': + return 'โ–ช '; + case 'decimal': + return '$index. '; + case 'decimal-leading-zero': + return '${index.toString().padLeft(2, '0')}. '; + case 'lower-alpha': + case 'lower-latin': + return '${_indexToAlpha(index, uppercase: false)}. '; + case 'upper-alpha': + case 'upper-latin': + return '${_indexToAlpha(index, uppercase: true)}. '; + case 'lower-roman': + return '${_indexToRoman(index).toLowerCase()}. '; + case 'upper-roman': + return '${_indexToRoman(index)}. '; + default: + return isOrdered ? '$index. ' : 'โ€ข '; + } + } + + String _indexToAlpha(int index, {required bool uppercase}) { + // 1โ†’a, 2โ†’b, โ€ฆ26โ†’z, 27โ†’aa, etc. + final base = uppercase ? 'A'.codeUnitAt(0) : 'a'.codeUnitAt(0); + var n = index; + final buf = StringBuffer(); + while (n > 0) { + n--; + buf.write(String.fromCharCode(base + n % 26)); + n ~/= 26; + } + return buf.toString().split('').reversed.join(); + } + + String _indexToRoman(int n) { + const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; + const syms = [ + 'M', + 'CM', + 'D', + 'CD', + 'C', + 'XC', + 'L', + 'XL', + 'X', + 'IX', + 'V', + 'IV', + 'I' + ]; + final buf = StringBuffer(); + var remaining = n; + for (var i = 0; i < vals.length; i++) { + while (remaining >= vals[i]) { + buf.write(syms[i]); + remaining -= vals[i]; + } + } + return buf.toString(); + } + String _extractNodeText(UDTNode node) { if (node is TextNode) return node.text; final buf = StringBuffer(); diff --git a/packages/hyper_render_core/lib/src/core/render_hyper_box_paint.dart b/packages/hyper_render_core/lib/src/core/render_hyper_box_paint.dart index f677e75..3f9f66d 100644 --- a/packages/hyper_render_core/lib/src/core/render_hyper_box_paint.dart +++ b/packages/hyper_render_core/lib/src/core/render_hyper_box_paint.dart @@ -707,8 +707,9 @@ extension _RenderHyperBoxPaint on RenderHyperBox { rect: rect, image: cached.image!, fit: _getBoxFit(fragment.style.backgroundSize), - filterQuality: - FilterQuality.medium, // Crisp rendering on retina displays + repeat: _getImageRepeat(fragment.style.backgroundRepeat), + alignment: _getBackgroundAlignment(fragment.style.backgroundPosition), + filterQuality: FilterQuality.medium, ); if (borderRadius != null) { diff --git a/packages/hyper_render_core/lib/src/model/computed_style.dart b/packages/hyper_render_core/lib/src/model/computed_style.dart index a32a181..cf8cdbd 100644 --- a/packages/hyper_render_core/lib/src/model/computed_style.dart +++ b/packages/hyper_render_core/lib/src/model/computed_style.dart @@ -386,6 +386,13 @@ class ComputedStyle { /// CSS overflow-wrap String? overflowWrap; + /// CSS list-style-type: disc | circle | square | decimal | lower-alpha | + /// upper-alpha | lower-roman | upper-roman | none + String? listStyleType; + + /// CSS list-style-position: outside (default) | inside + String? listStylePosition; + /// CSS text-shadow List? textShadow; @@ -414,6 +421,12 @@ class ComputedStyle { /// CSS background-size String? backgroundSize; + /// CSS background-repeat: repeat | repeat-x | repeat-y | no-repeat | space | round + String? backgroundRepeat; + + /// CSS background-position: top | center | bottom | left | right | "x y" + String? backgroundPosition; + // ============================================ // Layout Properties // ============================================ @@ -613,6 +626,8 @@ class ComputedStyle { this.textOverflow, this.wordBreak, this.overflowWrap, + this.listStyleType, + this.listStylePosition, this.textShadow, this.boxShadow, this.filter, @@ -621,6 +636,8 @@ class ComputedStyle { this.backgroundGradient, this.backgroundImage, this.backgroundSize, + this.backgroundRepeat, + this.backgroundPosition, this.display = DisplayType.inline, this.overflowX = HyperOverflow.visible, this.overflowY = HyperOverflow.visible, @@ -774,6 +791,8 @@ class ComputedStyle { TextOverflow? textOverflow, String? wordBreak, String? overflowWrap, + String? listStyleType, + String? listStylePosition, List? textShadow, List? boxShadow, ui.ImageFilter? filter, @@ -783,6 +802,8 @@ class ComputedStyle { Gradient? backgroundGradient, String? backgroundImage, String? backgroundSize, + String? backgroundRepeat, + String? backgroundPosition, // Layout DisplayType? display, HyperOverflow? overflowX, @@ -868,6 +889,8 @@ class ComputedStyle { textOverflow: textOverflow ?? this.textOverflow, wordBreak: wordBreak ?? this.wordBreak, overflowWrap: overflowWrap ?? this.overflowWrap, + listStyleType: listStyleType ?? this.listStyleType, + listStylePosition: listStylePosition ?? this.listStylePosition, textShadow: textShadow ?? this.textShadow, boxShadow: boxShadow ?? this.boxShadow, filter: filter ?? this.filter, @@ -876,6 +899,8 @@ class ComputedStyle { backgroundGradient: backgroundGradient ?? this.backgroundGradient, backgroundImage: backgroundImage ?? this.backgroundImage, backgroundSize: backgroundSize ?? this.backgroundSize, + backgroundRepeat: backgroundRepeat ?? this.backgroundRepeat, + backgroundPosition: backgroundPosition ?? this.backgroundPosition, display: display ?? this.display, overflowX: overflowX ?? this.overflowX, overflowY: overflowY ?? this.overflowY, diff --git a/packages/hyper_render_core/lib/src/style/resolver.dart b/packages/hyper_render_core/lib/src/style/resolver.dart index ce16959..fb1dda1 100644 --- a/packages/hyper_render_core/lib/src/style/resolver.dart +++ b/packages/hyper_render_core/lib/src/style/resolver.dart @@ -1214,6 +1214,27 @@ class StyleResolver { style.markExplicitlySet('background-size'); break; + case 'background-repeat': + final repeat = value.trim().toLowerCase(); + const validRepeats = { + 'repeat', + 'repeat-x', + 'repeat-y', + 'no-repeat', + 'space', + 'round', + }; + if (validRepeats.contains(repeat)) { + style.backgroundRepeat = repeat; + style.markExplicitlySet('background-repeat'); + } + break; + + case 'background-position': + style.backgroundPosition = value.trim().toLowerCase(); + style.markExplicitlySet('background-position'); + break; + case 'font-size': final size = _parseFontSize(value, parentFontSize: parentFontSize); if (size != null) { @@ -1565,6 +1586,64 @@ class StyleResolver { style.markExplicitlySet('overflow-wrap'); break; + case 'list-style-type': + final lst = value.trim().toLowerCase(); + const validListStyleTypes = { + 'disc', + 'circle', + 'square', + 'decimal', + 'decimal-leading-zero', + 'lower-alpha', + 'lower-latin', + 'upper-alpha', + 'upper-latin', + 'lower-roman', + 'upper-roman', + 'none', + }; + if (validListStyleTypes.contains(lst)) { + style.listStyleType = lst; + style.markExplicitlySet('list-style-type'); + } + break; + + case 'list-style-position': + final lsp = value.trim().toLowerCase(); + if (lsp == 'inside' || lsp == 'outside') { + style.listStylePosition = lsp; + style.markExplicitlySet('list-style-position'); + } + break; + + case 'list-style': + // Shorthand: parse each space-separated token + for (final token in value.trim().toLowerCase().split(RegExp(r'\s+'))) { + if (token == 'none') { + style.listStyleType = 'none'; + style.markExplicitlySet('list-style-type'); + } else if (token == 'inside' || token == 'outside') { + style.listStylePosition = token; + style.markExplicitlySet('list-style-position'); + } else if (const { + 'disc', + 'circle', + 'square', + 'decimal', + 'decimal-leading-zero', + 'lower-alpha', + 'lower-latin', + 'upper-alpha', + 'upper-latin', + 'lower-roman', + 'upper-roman', + }.contains(token)) { + style.listStyleType = token; + style.markExplicitlySet('list-style-type'); + } + } + break; + case 'text-shadow': final shadows = _parseTextShadow(value); if (shadows != null && shadows.isNotEmpty) { diff --git a/packages/hyper_render_core/lib/src/widgets/hyper_selection_overlay.dart b/packages/hyper_render_core/lib/src/widgets/hyper_selection_overlay.dart index 188f704..a54b842 100644 --- a/packages/hyper_render_core/lib/src/widgets/hyper_selection_overlay.dart +++ b/packages/hyper_render_core/lib/src/widgets/hyper_selection_overlay.dart @@ -116,9 +116,10 @@ class HyperSelectionOverlayState extends State /// Whether context menu is showing bool _showContextMenu = false; - /// Selection handles positions + /// Selection handles positions and rects (cached to avoid redundant passes) Rect? _startHandleRect; Rect? _endHandleRect; + List _selectionRects = const []; /// Which handle is being dragged (for potential future animation/haptic feedback) // ignore: unused_field @@ -294,9 +295,11 @@ class HyperSelectionOverlayState extends State final renderBox = _renderBox; if (renderBox == null) return; + final rects = renderBox.getSelectionRects(); setState(() { - _startHandleRect = renderBox.getStartHandleRect(); - _endHandleRect = renderBox.getEndHandleRect(); + _selectionRects = rects; + _startHandleRect = rects.isEmpty ? null : rects.first; + _endHandleRect = rects.isEmpty ? null : rects.last; }); } @@ -335,29 +338,18 @@ class HyperSelectionOverlayState extends State return KeyEventResult.ignored; } - /// Calculate menu position above selection + /// Calculate menu position above selection (uses cached rects โ€” no extra pass) Offset _calculateMenuPosition() { - if (_startHandleRect == null) return Offset.zero; + if (_selectionRects.isEmpty) return Offset.zero; - final RenderBox? box = - _renderKey.currentContext?.findRenderObject() as RenderBox?; - if (box == null) return Offset.zero; - - // Get all selection rects to find the topmost point - final selectionRects = _renderBox?.getSelectionRects() ?? []; - if (selectionRects.isEmpty) return Offset.zero; - - // Find the topmost and leftmost point of selection double minY = double.infinity; double centerX = 0; - for (final rect in selectionRects) { + for (final rect in _selectionRects) { if (rect.top < minY) { minY = rect.top; centerX = rect.center.dx; } } - - // Position menu above selection with some padding return Offset(centerX, minY - 8); } @@ -466,13 +458,15 @@ class HyperSelectionOverlayState extends State final localPosition = scrollableBox.globalToLocal(globalPosition); final size = scrollableBox.size; - const threshold = 50.0; + // Speed scales linearly with proximity: 0 px/event at threshold edge โ†’ 20 px/event at screen edge. + const threshold = 60.0; + const maxStep = 20.0; double dy = 0.0; if (localPosition.dy < threshold) { - dy = -15.0; + dy = -maxStep * (1.0 - localPosition.dy / threshold); } else if (localPosition.dy > size.height - threshold) { - dy = 15.0; + dy = maxStep * (1.0 - (size.height - localPosition.dy) / threshold); } if (dy != 0.0) { @@ -531,7 +525,7 @@ class HyperSelectionOverlayState extends State }, child: CustomPaint( size: const Size(22, 22), - painter: _TeardropHandlePainter( + painter: HyperTeardropHandlePainter( color: widget.handleColor, isStart: isStart, ), @@ -623,11 +617,11 @@ class _ContextMenuButton extends StatelessWidget { } /// Custom painter for native-style teardrop selection handles -class _TeardropHandlePainter extends CustomPainter { +class HyperTeardropHandlePainter extends CustomPainter { final Color color; final bool isStart; - _TeardropHandlePainter({ + HyperTeardropHandlePainter({ required this.color, required this.isStart, }); @@ -681,7 +675,7 @@ class _TeardropHandlePainter extends CustomPainter { } @override - bool shouldRepaint(_TeardropHandlePainter oldDelegate) { + bool shouldRepaint(HyperTeardropHandlePainter oldDelegate) { return color != oldDelegate.color || isStart != oldDelegate.isStart; } } diff --git a/test/style/css_bug_fixes_test.dart b/test/style/css_bug_fixes_test.dart index e348d1c..cab7f57 100644 --- a/test/style/css_bug_fixes_test.dart +++ b/test/style/css_bug_fixes_test.dart @@ -601,4 +601,94 @@ void main() { expect(linkNode!.style.color, const Color(0xFFFF0000)); }); }); + + // --------------------------------------------------------------------------- + // list-style-type / list-style-position / list-style shorthand + // --------------------------------------------------------------------------- + group('list-style-type', () { + test('default ul uses disc', () { + final style = _styleOfTag('
        • item
        ', 'li'); + // No explicit list-style-type โ†’ null (resolved to disc in layout) + expect(style?.listStyleType, isNull); + }); + + test('list-style-type: none is parsed', () { + final style = _styleOfTag( + '
        • item
        ', + 'ul', + ); + expect(style?.listStyleType, 'none'); + }); + + test('list-style-type: circle on li', () { + final style = _styleOfTag( + '
        • item
        ', + 'li', + ); + expect(style?.listStyleType, 'circle'); + }); + + test('list-style-type: upper-roman', () { + final style = _styleOfTag( + '
        1. item
        ', + 'ol', + ); + expect(style?.listStyleType, 'upper-roman'); + }); + + test('list-style-position: inside', () { + final style = _styleOfTag( + '
        • item
        ', + 'ul', + ); + expect(style?.listStylePosition, 'inside'); + }); + + test('list-style shorthand: none sets type to none', () { + final style = _styleOfTag( + '
        • item
        ', + 'ul', + ); + expect(style?.listStyleType, 'none'); + }); + + test('list-style shorthand: square inside', () { + final style = _styleOfTag( + '
        • item
        ', + 'ul', + ); + expect(style?.listStyleType, 'square'); + expect(style?.listStylePosition, 'inside'); + }); + + test('invalid value is ignored', () { + final style = _styleOfTag( + '
        • item
        ', + 'ul', + ); + expect(style?.listStyleType, isNull); + }); + + testWidgets('list-style:none nav menu renders without crash', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: HyperViewer( + html: ''' +''', + ), + ), + ), + ); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + }); } From cad7770c655fce6849f7ceb695d745eeebcd6ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 14 May 2026 12:36:16 +0700 Subject: [PATCH 4/7] fix(example): remove hyper_render_clipboard from example app to fix iOS/Android native build failures --- example/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 472f3c1..32bfdf7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -22,8 +22,6 @@ dependencies: path: ../packages/hyper_render_core # Clipboard/sharing support - hyper_render_clipboard: - path: ../packages/hyper_render_clipboard # Competitor libraries for comparison flutter_html: ^3.0.0-beta.2 @@ -46,8 +44,6 @@ dependency_overrides: path: ../ hyper_render_core: path: ../packages/hyper_render_core - hyper_render_clipboard: - path: ../packages/hyper_render_clipboard dev_dependencies: flutter_test: From 2be27343abb93881570ab5516bdbe5ad3d44785c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 14 May 2026 12:40:21 +0700 Subject: [PATCH 5/7] fix(example): remove outdated compileSdk workaround from android gradle configuration --- example/android/build.gradle.kts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts index 61ec193..8c589a6 100644 --- a/example/android/build.gradle.kts +++ b/example/android/build.gradle.kts @@ -19,25 +19,6 @@ subprojects { project.evaluationDependsOn(":app") } -// Workaround: irondash_engine_context 0.5.5 was compiled against android-31 but -// transitively requires androidx.fragment:1.7.1 which has minCompileSdk=34. -// AGP 8 checkAarMetadata blocks the build. Override compileSdk for all library -// subprojects so the check passes. -// Tracked: https://github.com/brewkits/hyper_render/issues/5 -subprojects { - val project = this - if (project.state.executed) { - project.extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply { - compileSdk = 35 - } - } else { - project.afterEvaluate { - project.extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply { - compileSdk = 35 - } - } - } -} tasks.register("clean") { delete(rootProject.layout.buildDirectory) From b6a417aa44ff15d415e1d488ecfc513ce1e0ab07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?= Date: Thu, 14 May 2026 19:28:35 +0700 Subject: [PATCH 6/7] docs: add 1.3.1 changelog entries, ecosystem cross-links, and migration guide for all packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add [1.3.1] CHANGELOG entries to all 7 sub-packages (pub.dev requires version match) - Update all sub-package READMEs: bump version references ^1.3.0 โ†’ ^1.3.1, add ecosystem cross-link table with "you are here" marker, add License section where missing - Fix hyper_render_core README plugin API (tagNameโ†’tagNames, build()โ†’buildWidget()) - Add list-style-type / background-repeat / background-position to hyper_render_html CSS table - Fix hyper_render_math README: core dep removed (only hyper_render_math needed now) - Update root CHANGELOG 1.3.1: add CSS features, selection perf, full build notes - Update doc/MIGRATION_GUIDE.md for 1.3.1 breaking change (clipboard/math opt-in) - Fix scripts/prepare_publish.sh: swap path dep version ^1.3.0 โ†’ ^1.3.1 - Demo improvements: padding, contrast fixes, scroll wrappers, zero_padding_image_demo --- CHANGELOG.md | 37 +- doc/MIGRATION_GUIDE.md | 58 ++-- example/lib/cjk_languages_demo.dart | 322 +++++++++-------- example/lib/email_demo.dart | 2 +- example/lib/main.dart | 324 ++++++++++++------ example/lib/manga_demo.dart | 276 ++++++++------- example/lib/stress_test_demo.dart | 23 +- example/lib/why_hyper_render_demo.dart | 53 ++- example/lib/zero_padding_image_demo.dart | 152 ++++++++ packages/hyper_render_clipboard/CHANGELOG.md | 5 + packages/hyper_render_clipboard/README.md | 19 +- packages/hyper_render_core/CHANGELOG.md | 17 + packages/hyper_render_core/README.md | 25 +- .../lib/src/core/render_hyper_box_layout.dart | 2 +- packages/hyper_render_devtools/CHANGELOG.md | 5 + packages/hyper_render_devtools/README.md | 19 +- packages/hyper_render_highlight/CHANGELOG.md | 5 + packages/hyper_render_highlight/README.md | 21 +- packages/hyper_render_html/CHANGELOG.md | 5 + packages/hyper_render_html/README.md | 25 +- packages/hyper_render_markdown/CHANGELOG.md | 5 + packages/hyper_render_markdown/README.md | 21 +- packages/hyper_render_math/CHANGELOG.md | 6 + packages/hyper_render_math/README.md | 25 +- scripts/prepare_publish.sh | 4 +- 25 files changed, 991 insertions(+), 465 deletions(-) create mode 100644 example/lib/zero_padding_image_demo.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 621d6d1..572331e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,40 @@ # Changelog -## [1.3.1] - 2026-05-04 +## [1.3.1] - 2026-05-14 -### ๐Ÿ—๏ธ Maintenance & Build Fixes +### โš ๏ธ Migration from 1.3.0 -- **Decoupled Native Dependencies**: Removed `hyper_render_clipboard` (which relies on `super_clipboard`) and `hyper_render_math` from the default `hyper_render` wrapper package. This eliminates the `compileSdk = 34` requirement out-of-the-box, fixing Android build failures for users on older Gradle setups. -- **Optional Add-ons**: Native clipboard and LaTeX math rendering are now opt-in. Install `hyper_render_clipboard` or `hyper_render_math` directly if needed. +`hyper_render_clipboard` and `hyper_render_math` are no longer transitive dependencies of `hyper_render`. If you use either, add them explicitly: + +```yaml +dependencies: + hyper_render: ^1.3.1 + hyper_render_clipboard: ^1.3.1 # only if you use SuperClipboardHandler + hyper_render_math: ^1.3.1 # only if you use MathNodePlugin / LatexNodePlugin +``` + +### โœจ New CSS Properties + +- **`list-style-type`**: All 11 marker types โ€” `disc`, `circle`, `square`, `decimal`, `decimal-leading-zero`, `lower-alpha`, `upper-alpha`, `lower-latin`, `upper-latin`, `lower-roman`, `upper-roman`, `none` +- **`list-style-position`**: `inside` / `outside` +- **`list-style` shorthand**: parses type and position in any order +- **`background-repeat`**: `repeat`, `repeat-x`, `repeat-y`, `no-repeat`, `space`, `round` +- **`background-position`**: keyword (`center`, `top left`, etc.) and percentage values + +### ๐Ÿš€ Performance + +- **Selection rects cached**: `getSelectionRects()` now called once per drag event (was 3ร—) โ€” stored in `_selectionRects` field, eliminating redundant layout walks during selection drag +- **Auto-scroll proportional speed**: `_autoScrollIfNearEdge` scales 0โ€“20 px/frame based on finger proximity to edge (was fixed 15 px/frame) +- **`HyperTeardropHandlePainter` deduplicated**: renamed, made public, and exported from `hyper_render_core`; duplicate implementation in the virtualized overlay removed + +### ๐Ÿ› Bug Fixes + +- **Edge-to-edge images**: `width: 100%` images now truly fill their container โ€” no internal margin offset + +### ๐Ÿ—๏ธ Build Fixes + +- **Decoupled native dependencies**: `hyper_render_clipboard` and `hyper_render_math` removed from root `hyper_render` default dependencies โ€” eliminates the `compileSdk = 34` Gradle requirement for basic usage +- **Removed outdated `compileSdk` workaround** from example app's Android Gradle config ## [1.3.0] - 2026-05-03 diff --git a/doc/MIGRATION_GUIDE.md b/doc/MIGRATION_GUIDE.md index 7a902fb..e1bec01 100644 --- a/doc/MIGRATION_GUIDE.md +++ b/doc/MIGRATION_GUIDE.md @@ -1,17 +1,41 @@ # Migration Guide -> **Current version: v1.2.0** โ€” All v1.x releases are additive and backward-compatible. No breaking API changes. +> **Current version: v1.3.1** -## Current Version: 1.2.0 +## Upgrading to 1.3.1 -**No migration needed!** If you're starting fresh with HyperRender v1.2.0: +### โš ๏ธ Breaking change โ€” clipboard and math are now opt-in + +`hyper_render_clipboard` and `hyper_render_math` are no longer transitive dependencies of the root `hyper_render` package. If you use either, add them explicitly: + +```yaml +dependencies: + hyper_render: ^1.3.1 + hyper_render_clipboard: ^1.3.1 # only if you use SuperClipboardHandler + hyper_render_math: ^1.3.1 # only if you use MathNodePlugin / LatexNodePlugin +``` + +If you don't use either feature, **no changes are needed** โ€” just bump the version and your Android build will no longer require a `compileSdk = 35` workaround. + +### New in 1.3.1 + +- `list-style-type`, `list-style-position`, `list-style` shorthand CSS support +- `background-repeat`, `background-position` CSS support +- Edge-to-edge images: `width: 100%` now truly fills the container +- Selection drag performance improved (rects cached, auto-scroll proportional) + +--- + +## Starting fresh with 1.3.1 + +**No migration needed!** If you're starting fresh: ```yaml dependencies: - hyper_render: ^1.2.0 - # or use individual packages: - hyper_render_core: ^1.2.0 - hyper_render_clipboard: ^1.2.0 + hyper_render: ^1.3.1 + # opt-in extras: + hyper_render_clipboard: ^1.3.1 # image copy/save/share + hyper_render_math: ^1.3.1 # LaTeX/MathML ``` ```dart @@ -156,7 +180,7 @@ These APIs are stable and will remain backward-compatible in v2.0: ## Getting Help -For the current v1.3.0 release: +For the current v1.3.1 release: - See [README](../README.md) for usage - Check [CHANGELOG](../CHANGELOG.md) for version history - Review [Plugin Development Guide](PLUGIN_DEVELOPMENT.md) for extending @@ -164,20 +188,4 @@ For the current v1.3.0 release: --- -*Last Updated: April 29, 2026 for v1.3.0* -ard support -- Cross-platform (iOS, Android, Web, Desktop) - ---- - -## Getting Help - -For the current v1.2.0 release: -- See [README](../README.md) for usage -- Check [CHANGELOG](../CHANGELOG.md) for version history -- Review [Plugin Development Guide](PLUGIN_DEVELOPMENT.md) for extending -- File issues at [GitHub Issues](https://github.com/your-repo/issues) - ---- - -*Last Updated: March 30, 2026 for v1.2.0* +*Last Updated: May 14, 2026 for v1.3.1* diff --git a/example/lib/cjk_languages_demo.dart b/example/lib/cjk_languages_demo.dart index b1b8073..209e151 100644 --- a/example/lib/cjk_languages_demo.dart +++ b/example/lib/cjk_languages_demo.dart @@ -72,62 +72,64 @@ class _SimplifiedChineseTab extends StatelessWidget { const _SimplifiedChineseTab(); static const _html = ''' -
        +
        -

        Flutter ่ทจๅนณๅฐๅผ€ๅ‘ๆŒ‡ๅ—

        -

        + padding: 24px 20px; border-radius: 12px; margin-bottom: 28px; box-shadow: 0 4px 12px rgba(21,101,192,0.15);"> +

        Flutter ่ทจๅนณๅฐๅผ€ๅ‘ๆŒ‡ๅ—

        +

        ๆŠ€ๆœฏๆทฑๅบฆ่งฃๆž ยท 2024ๅนด็‰ˆ

        -

        ไธ€ใ€ๆ ธๅฟƒไผ˜ๅŠฟ

        -

        +

        ไธ€ใ€ๆ ธๅฟƒไผ˜ๅŠฟ

        +

        Flutter ๆ˜ฏ็”ฑ Google ๅผ€ๅ‘็š„ๅผ€ๆบ UI ๆก†ๆžถ๏ผŒๅฏไปฅไปŽๅ•ไธ€ไปฃ็ ๅบ“ๆž„ๅปบ้€‚็”จไบŽ ็งปๅŠจ็ซฏใ€Web ๅ’ŒๆกŒ้ข็š„็ฒพ็พŽๅŽŸ็”Ÿ็ผ–่ฏ‘ๅบ”็”จใ€‚ไธŽไผ ็ปŸ็š„่ทจๅนณๅฐๆ–นๆกˆไธๅŒ๏ผŒFlutter ไฝฟ็”จ่‡ชๅทฑ็š„ ๆธฒๆŸ“ๅผ•ๆ“Ž Skia / Impeller๏ผŒๅฎŒๅ…จไธไพ่ต–ๅนณๅฐๅŽŸ็”ŸๆŽงไปถใ€‚

        -
        -

        +

        +

        "ไธ€ๆฌก็ผ–ๅ†™๏ผŒๅˆฐๅค„่ฟ่กŒโ€”โ€”่ฟ™ไธๅ†ๅชๆ˜ฏๅฃๅท๏ผŒFlutter ่ฎฉๅฎƒๆˆไธบ็Žฐๅฎžใ€‚"

        -

        โ€” Google ๅผ€ๅ‘่€…ๅคงไผš๏ผŒ2023

        +

        โ€” Google ๅผ€ๅ‘่€…ๅคงไผš๏ผŒ2023

        -

        ไบŒใ€ไธป่ฆ็‰นๆ€งๅฏนๆฏ”

        - - - - - - - - - - - - - - - - - - - - - - - - - -
        ๆก†ๆžถ็ƒญ้‡่ฝฝๆธฒๆŸ“ๅผ•ๆ“Žๆ€ง่ƒฝ
        Flutterโœ… ไบš็ง’็บง่‡ช็ ” Impellerโญโญโญโญโญ
        React Nativeโœ… Fast RefreshๅนณๅฐๅŽŸ็”Ÿโญโญโญโญ
        Xamarinโš ๏ธ ๆœ‰้™ๅนณๅฐๅŽŸ็”Ÿโญโญโญ
        - -

        ไธ‰ใ€Dart ่ฏญ่จ€็คบไพ‹

        +

        ไบŒใ€ไธป่ฆ็‰นๆ€งๅฏนๆฏ”

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        ๆก†ๆžถ็ƒญ้‡่ฝฝๆธฒๆŸ“ๅผ•ๆ“Žๆ€ง่ƒฝ
        Flutterโœ… ไบš็ง’็บง่‡ช็ ” Impellerโญโญโญโญโญ
        React Nativeโœ… Fast RefreshๅนณๅฐๅŽŸ็”Ÿโญโญโญโญ
        Xamarinโš ๏ธ ๆœ‰้™ๅนณๅฐๅŽŸ็”Ÿโญโญโญ
        +
        + +

        ไธ‰ใ€Dart ่ฏญ่จ€็คบไพ‹

        // ๅผ‚ๆญฅ็ผ–็จ‹็คบไพ‹
        +              font-size: 14px; overflow: auto; line-height: 1.6; margin-bottom: 24px;">// ๅผ‚ๆญฅ็ผ–็จ‹็คบไพ‹
         Future<void> ๅŠ ่ฝฝ็”จๆˆทๆ•ฐๆฎ() async {
           try {
             final ๆ•ฐๆฎ = await ็ฝ‘็ปœ่ฏทๆฑ‚('/api/็”จๆˆท');
        @@ -135,10 +137,10 @@ Future<void> ๅŠ ่ฝฝ็”จๆˆทๆ•ฐๆฎ() async {
           } catch (้”™่ฏฏ) {
             print('ๅŠ ่ฝฝๅคฑ่ดฅ: \$้”™่ฏฏ');
           }
        -}
        +}
    -

    ๅ››ใ€็”Ÿๆ€็ณป็ปŸ

    -
      +

      ๅ››ใ€็”Ÿๆ€็ณป็ปŸ

      +
      • pub.dev โ€” ่ถ…่ฟ‡ 30,000 ไธชๅผ€ๆบ package
      • Flutter DevTools โ€” ๅผบๅคง็š„ๆ€ง่ƒฝๅˆ†ๆžๅ’Œ่ฐƒ่ฏ•ๅทฅๅ…ท
      • Firebase โ€” ๅฎŒๆ•ด็š„ๅŽ็ซฏๆœๅŠก้›†ๆˆ
      • @@ -146,9 +148,9 @@ Future<void> ๅŠ ่ฝฝ็”จๆˆทๆ•ฐๆฎ() async {
      • Widget ๆต‹่ฏ• โ€” ๅฎŒๆ•ด็š„ UI ่‡ชๅŠจๅŒ–ๆต‹่ฏ•ๆ”ฏๆŒ
      -
      -

      +

      +

      ๐Ÿ’ก ๅฐ่ดดๅฃซ๏ผšๅœจไธญๆ–‡ๆŽ’็‰ˆไธญ๏ผŒHyperRender ไผš่‡ชๅŠจๅค„็†ๆฑ‰ๅญ—้—ด่ทใ€ ๆ ‡็‚น็ฆๅˆ™่ง„ๅˆ™๏ผˆ็ฆๆญขๅœจ่กŒ้ฆ–ๅ‡บ็Žฐๅฅๅทใ€้€—ๅท็ญ‰ๆ ‡็‚น๏ผ‰๏ผŒไปฅๅŠไธญ่‹ฑๆ–‡ๆททๆŽ’ๆ—ถ็š„้—ด่ทไผ˜ๅŒ–ใ€‚

      @@ -159,10 +161,14 @@ Future<void> ๅŠ ่ฝฝ็”จๆˆทๆ•ฐๆฎ() async { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: HyperViewer(html: _html, selectable: true), + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), + child: HyperViewer(html: _html, selectable: true), + ), ), ); } @@ -176,86 +182,88 @@ class _TraditionalChineseTab extends StatelessWidget { const _TraditionalChineseTab(); static const _html = ''' -
      +
      -

      ๅคๅ…ธๆ–‡ๅญธ้ธ็ฒน

      -

      + padding: 24px 20px; border-radius: 12px; margin-bottom: 28px; box-shadow: 0 4px 12px rgba(136,14,79,0.15);"> +

      ๅคๅ…ธๆ–‡ๅญธ้ธ็ฒน

      +

      ๅ”่ฉฉ ยท ๅฎ‹่ฉž ยท ๅคๆ–‡ๅ็ฏ‡

      -

      +

      ใ€ˆ้œๅคœๆ€ใ€‰โ€” ๆŽ็™ฝ

      -
      -

      ๅบŠๅ‰ๆ˜Žๆœˆๅ…‰๏ผŒ
      ็–‘ๆ˜ฏๅœฐไธŠ้œœใ€‚
      ่ˆ‰้ ญๆœ›ๆ˜Žๆœˆ๏ผŒ
      ไฝŽ้ ญๆ€ๆ•…้„‰ใ€‚

      -

      +

      โ€”โ€” ๅ” ยท ๆŽ็™ฝ๏ผˆ701โ€”762ๅนด๏ผ‰

      -

      +

      ใ€ˆๆฐด่ชฟๆญŒ้ ญใ€‰โ€” ่˜‡่ปพ

      -
      -

      +

      ๆ˜Žๆœˆๅนพๆ™‚ๆœ‰๏ผŸๆŠŠ้…’ๅ•้’ๅคฉใ€‚
      ไธ็ŸฅๅคฉไธŠๅฎฎ้—•๏ผŒไปŠๅค•ๆ˜ฏไฝ•ๅนดใ€‚
      ๆˆ‘ๆฌฒไน˜้ขจๆญธๅŽป๏ผŒๅˆๆ็“Šๆจ“็މๅฎ‡๏ผŒ
      ้ซ˜่™•ไธๅ‹ๅฏ’ใ€‚
      ่ตท่ˆžๅผ„ๆธ…ๅฝฑ๏ผŒไฝ•ไผผๅœจไบบ้–“ใ€‚

      -

      +

      ่ฝ‰ๆœฑ้–ฃ๏ผŒไฝŽ็ถบๆˆถ๏ผŒ็…ง็„ก็œ ใ€‚
      ไธๆ‡‰ๆœ‰ๆจ๏ผŒไฝ•ไบ‹้•ทๅ‘ๅˆฅๆ™‚ๅœ“๏ผŸ
      ไบบๆœ‰ๆ‚ฒๆญก้›ขๅˆ๏ผŒๆœˆๆœ‰้™ฐๆ™ดๅœ“็ผบ๏ผŒ
      ๆญคไบ‹ๅค้›ฃๅ…จใ€‚
      ไฝ†้ก˜ไบบ้•ทไน…๏ผŒๅƒ้‡Œๅ…ฑๅฌ‹ๅจŸใ€‚

      -

      +

      โ€”โ€” ๅฎ‹ ยท ่˜‡่ปพ๏ผˆ1037โ€”1101ๅนด๏ผ‰

      -

      +

      ๅคๆ–‡็ถ“ๅ…ธๆณจ้‡‹

      - - - - - - - - - - - - - - - - - - - - - -
      ่ฉž่ชžๆณจ้Ÿณ้‡‹็พฉ
      ๅฌ‹ๅจŸchรกn juฤn็พŽๅฅฝใ€ๆŒ‡ๆœˆไบฎ๏ผ›ไบฆๅ–ปๆ€ๅฟตไน‹ไบบ
      ็“Šๆจ“็މๅฎ‡qiรณng lรณu yรน yว”ๅฝขๅฎนไป™ๅขƒไธญ็พŽ้บ—็š„ๅฎฎๆฎฟๆจ“้–ฃ
      ็ถบๆˆถqว hรน้›•ๅˆป็ฒพ็พŽ็š„้–€็ช—๏ผ›ๆณ›ๆŒ‡่ฏ้บ—็š„ๅฑ…ๆ‰€
      - -
      -

      +

      + + + + + + + + + + + + + + + + + + + + + +
      ่ฉž่ชžๆณจ้Ÿณ้‡‹็พฉ
      ๅฌ‹ๅจŸchรกn juฤn็พŽๅฅฝใ€ๆŒ‡ๆœˆไบฎ๏ผ›ไบฆๅ–ปๆ€ๅฟตไน‹ไบบ
      ็“Šๆจ“็މๅฎ‡qiรณng lรณu yรน yว”ๅฝขๅฎนไป™ๅขƒไธญ็พŽ้บ—็š„ๅฎฎๆฎฟๆจ“้–ฃ
      ็ถบๆˆถqว hรน้›•ๅˆป็ฒพ็พŽ็š„้–€็ช—๏ผ›ๆณ›ๆŒ‡่ฏ้บ—็š„ๅฑ…ๆ‰€
      +
      + +
      +

      ๐Ÿ“– ๆŽ’็‰ˆ่ชชๆ˜Ž๏ผšHyperRender ๆ”ฏๆด็น้ซ”ไธญๆ–‡็š„ๆญฃ็ขบๆธฒๆŸ“๏ผŒๅŒ…ๅซ ๆจ™้ปž็ฌฆ่™Ÿ็ฆๅ‰‡๏ผˆ้ฟ้ ญๅฐพ่ฆๅ‰‡๏ผ‰ใ€ๅ…จๅฝขๆจ™้ปž่ˆ‡ๅŠๅฝข่‹ฑๆ•ธๆททๆŽ’้–“่ท๏ผŒไปฅๅŠ ้ซ˜ๅฏ†ๅบฆๆผขๅญ—ๆ–‡ๆœฌ็š„็ฒพ็ขบๆ›่กŒ่จˆ็ฎ—ใ€‚ @@ -267,10 +275,14 @@ class _TraditionalChineseTab extends StatelessWidget { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: HyperViewer(html: _html, selectable: true), + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), + child: HyperViewer(html: _html, selectable: true), + ), ), ); } @@ -284,93 +296,95 @@ class _KoreanTab extends StatelessWidget { const _KoreanTab(); static const _html = ''' -

      +
      -

      ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ ํŠธ๋ Œ๋“œ

      -

      + padding: 24px 20px; border-radius: 12px; margin-bottom: 28px; box-shadow: 0 4px 12px rgba(0,77,64,0.15);"> +

      ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ ํŠธ๋ Œ๋“œ

      +

      2024๋…„ ๊ธฐ์ˆ  ๋™ํ–ฅ ๋ถ„์„ ๋ฆฌํฌํŠธ

      -

      ๐Ÿ“ฑ ํฌ๋กœ์Šคํ”Œ๋žซํผ์˜ ๋ถ€์ƒ

      -

      +

      ๐Ÿ“ฑ ํฌ๋กœ์Šคํ”Œ๋žซํผ์˜ ๋ถ€์ƒ

      +

      ์ตœ๊ทผ ๋ช‡ ๋…„๊ฐ„ ํฌ๋กœ์Šคํ”Œ๋žซํผ ๊ฐœ๋ฐœ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์„ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. Flutter, React Native, Kotlin Multiplatform ๋“ฑ์˜ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ iOS์™€ Android๋ฅผ ๋™์‹œ์— ์ง€์›ํ•˜๋ฉด์„œ ๊ฐœ๋ฐœ ๋น„์šฉ์„ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

      -
      -

      +

      +

      "Flutter๋Š” ๋‹จ์ˆœํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์•„๋‹ˆ๋ผ, ์ƒˆ๋กœ์šด ๋ฐฉ์‹์˜ UI ํŒจ๋Ÿฌ๋‹ค์ž„์ž…๋‹ˆ๋‹ค. Dart ์–ธ์–ด์˜ ์„ฑ๋Šฅ๊ณผ Hot Reload์˜ ํŽธ์˜์„ฑ์ด ๊ฒฐํ•ฉ๋˜์–ด ์ƒ์‚ฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค."

      -

      โ€” Google I/O 2024

      +

      โ€” Google I/O 2024

      -

      ๐Ÿ“Š ์ฃผ์š” ํ”„๋ ˆ์ž„์›Œํฌ ๋น„๊ต

      - - - - - - - - - - - - - - - - - - - - - - - - - -
      ํ”„๋ ˆ์ž„์›Œํฌ์–ธ์–ด์„ฑ๋Šฅํ•™์Šต ๋‚œ์ด๋„
      FlutterDart๋งค์šฐ ์šฐ์ˆ˜์ค‘๊ฐ„
      React NativeJavaScript์šฐ์ˆ˜๋‚ฎ์Œ
      KMPKotlin๋„ค์ดํ‹ฐ๋ธŒ ์ˆ˜์ค€๋†’์Œ
      - -

      ๐Ÿ”‘ ํ•ต์‹ฌ ์šฉ์–ด ์„ค๋ช…

      -
      -
      -
      +

      ๐Ÿ“Š ์ฃผ์š” ํ”„๋ ˆ์ž„์›Œํฌ ๋น„๊ต

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      ํ”„๋ ˆ์ž„์›Œํฌ์–ธ์–ด์„ฑ๋Šฅํ•™์Šต ๋‚œ์ด๋„
      FlutterDart๋งค์šฐ ์šฐ์ˆ˜์ค‘๊ฐ„
      React NativeJavaScript์šฐ์ˆ˜๋‚ฎ์Œ
      KMPKotlin๋„ค์ดํ‹ฐ๋ธŒ ์ˆ˜์ค€๋†’์Œ
      +
      + +

      ๐Ÿ”‘ ํ•ต์‹ฌ ์šฉ์–ด ์„ค๋ช…

      +
      +
      +
      ์œ„์ ฏ ํŠธ๋ฆฌ (Widget Tree)
      -
      +
      Flutter์˜ UI ๊ตฌ์„ฑ ์š”์†Œ๋“ค์ด ๊ณ„์ธต์ ์œผ๋กœ ๋ฐฐ์น˜๋œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฒƒ์ด ์œ„์ ฏ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋กœ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
      -
      -
      +
      +
      ์ƒํƒœ ๊ด€๋ฆฌ (State Management)
      -
      +
      ์•ฑ์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ ๋กœ, Riverpod, Bloc, Provider ๋“ฑ ๋‹ค์–‘ํ•œ ์†”๋ฃจ์…˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๊ทœ๋ชจ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
      -
      -
      +
      +
      ํ•ซ ๋ฆฌ๋กœ๋“œ (Hot Reload)
      -
      +
      ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์•ฑ์„ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ  ์ฆ‰์‹œ ๋ฐ˜์˜ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํš๊ธฐ์ ์œผ๋กœ ํ–ฅ์ƒ์‹œ์ผœ์ค๋‹ˆ๋‹ค.
      -

      โœ… ๊ฐœ๋ฐœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

      -
        +

        โœ… ๊ฐœ๋ฐœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

        +
        • ์•ฑ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ๋ฐ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐฉ์‹ ๊ฒฐ์ •
        • ๋””์ž์ธ ์‹œ์Šคํ…œ ๋ฐ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌ์ถ•
        • CI/CD ํŒŒ์ดํ”„๋ผ์ธ ์„ค์ • (GitHub Actions, Codemagic)
        • @@ -379,9 +393,9 @@ class _KoreanTab extends StatelessWidget {
        • ์•ฑ์Šคํ† ์–ด ์ œ์ถœ ๋ฐ ์‹ฌ์‚ฌ ๋Œ€์‘ ์ „๋žต
        -
        -

        +

        +

        ๐Ÿ’ก ๋ Œ๋”๋ง ์ฐธ๊ณ : HyperRender๋Š” ํ•œ๊ธ€ ์ž๋ชจ ๋ถ„๋ฆฌ ์—†์ด ์Œ์ ˆ ๋‹จ์œ„๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ค„ ๋ฐ”๊ฟˆ์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ์˜๋ฌธยท์ˆซ์žยทํ•œ๊ธ€ ํ˜ผ์šฉ ๋ฌธ์„œ์—์„œ๋„ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ„๊ฒฉ๊ณผ ์ค„ ๋‚˜๋ˆ”์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. @@ -393,10 +407,14 @@ class _KoreanTab extends StatelessWidget { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: HyperViewer(html: _html, selectable: true), + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), + child: HyperViewer(html: _html, selectable: true), + ), ), ); } diff --git a/example/lib/email_demo.dart b/example/lib/email_demo.dart index c609c14..6967ee8 100644 --- a/example/lib/email_demo.dart +++ b/example/lib/email_demo.dart @@ -144,7 +144,7 @@ class _EmailTab extends StatelessWidget { // Email body โ€” scrollable, padded like a real email client viewport Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 10), + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: HyperViewer( html: html, selectable: true, diff --git a/example/lib/main.dart b/example/lib/main.dart index b130f82..b342d25 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -36,6 +36,7 @@ import 'paged_mode_demo.dart'; import 'plugin_api_demo.dart'; import 'reader_app/library_screen.dart'; import 'float_hell_demo.dart'; +import 'zero_padding_image_demo.dart'; /// Optimized base TextStyle for better readability /// - fontSize: 16 (comfortable reading size) @@ -69,7 +70,7 @@ class HyperRenderDemoApp extends StatelessWidget { title: 'HyperRender Demo', debugShowCheckedModeBanner: false, theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo), + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1A56DB)), useMaterial3: true, ), scrollBehavior: const MaterialScrollBehavior().copyWith( @@ -96,9 +97,9 @@ class DemoHomePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('HyperRender Demo'), + title: const Text('HyperRender'), centerTitle: false, - backgroundColor: Theme.of(context).colorScheme.primary, + backgroundColor: const Color(0xFF1A56DB), foregroundColor: Colors.white, elevation: 0, ), @@ -110,42 +111,40 @@ class DemoHomePage extends StatelessWidget { const SizedBox(height: 16), _buildWhyCard(context), const SizedBox(height: 8), - // โ”€โ”€ The Ultimate Showcase โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - _buildSectionHeader(context, 'The Ultimate Showcase'), + // โ”€โ”€ Signature Features โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + _buildSectionHeader(context, 'Signature Features'), _buildDemoCard( context, icon: Icons.auto_awesome, - title: 'Ultra Showcase 2026', + title: 'Flagship Demo', subtitle: - 'Float + CJK Typography, Giant Div Virtualization, and Interactive Plugins', - color: Colors.redAccent, + 'Float layout ยท CJK ruby ยท virtualized rendering ยท plugin injection โ€” all at once', + color: const Color(0xFF1A56DB), onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const UltraShowcase2026())), ), - // โ”€โ”€ Highlights โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - _buildSectionHeader(context, 'Highlights'), _buildDemoCard( context, icon: Icons.view_quilt, - title: 'Float Layout', + title: 'CSS Float Layout', subtitle: - 'Text wraps around floated images โ€” the feature no other Flutter HTML library has', - color: DemoColors.primary, + 'Text wraps around floated images โ€” impossible in any widget-tree-based renderer', + color: const Color(0xFF1A56DB), onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FloatLayoutDemo())), ), _buildDemoCard( context, - icon: Icons.email, + icon: Icons.email_outlined, title: 'HTML Email', subtitle: - 'Render real HTML emails natively โ€” no WebView, no heavy dependencies', - color: DemoColors.primary, + 'Real HTML emails rendered natively โ€” no WebView, <1 MB overhead vs ~20 MB', + color: const Color(0xFF1A56DB), onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => const EmailDemo())), ), // โ”€โ”€ Applications โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - _buildSectionHeader(context, 'Applications & Solutions'), + _buildSectionHeader(context, 'Real-World Applications'), _buildDemoCard( context, icon: Icons.auto_stories, @@ -264,6 +263,16 @@ class DemoHomePage extends StatelessWidget { onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const _MediaHubPage())), ), + _buildDemoCard( + context, + icon: Icons.fullscreen, + title: 'Zero Padding Images', + subtitle: + 'Edge-to-edge images with no padding relative to the device edges', + color: DemoColors.warning, + onTap: () => Navigator.push(context, + MaterialPageRoute(builder: (_) => const ZeroPaddingImageDemo())), + ), _buildDemoCard( context, icon: Icons.widgets, @@ -298,24 +307,24 @@ class DemoHomePage extends StatelessWidget { onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FormulaDemo())), ), - // โ”€โ”€ Advanced & Quality โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - _buildSectionHeader(context, 'Advanced & Quality'), + // โ”€โ”€ Engineering & Quality โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + _buildSectionHeader(context, 'Engineering & Quality'), _buildDemoCard( context, - icon: Icons.whatshot, - title: 'Sprint 1: Float Hell Stress Test', + icon: Icons.speed, + title: 'Float Layout Stress Test', subtitle: - '2000 blocks, randomized left/right floats, width animation, virtualization test.', - color: Colors.deepPurple, + '2 000 blocks, randomised left/right floats, live width animation โ€” 60 FPS', + color: DemoColors.success, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FloatHellDemo())), ), _buildDemoCard( context, - icon: Icons.compare, + icon: Icons.compare_arrows_rounded, title: 'Comparison & Performance', subtitle: - 'Side-by-side vs other libraries, unique features, stress test, render pipeline', + 'Side-by-side vs other libraries, render pipeline deep-dive, benchmark numbers', color: DemoColors.success, onTap: () => Navigator.push( context, @@ -325,30 +334,30 @@ class DemoHomePage extends StatelessWidget { _buildDemoCard( context, icon: Icons.dark_mode, - title: 'Dark Mode, Skeletons & Visual Quality', + title: 'Dark Mode & Visual Quality', subtitle: - 'Theme switching, skeleton loading, error boundaries, crisp rendering', + 'Theme switching, skeleton loading, error boundaries, crisp retina rendering', color: DemoColors.success, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const V21Showcase())), ), _buildDemoCard( context, - icon: Icons.security, + icon: Icons.shield_outlined, title: 'Security & Accessibility', subtitle: - 'XSS protection, screen reader support, WebView fallback detection', + 'XSS sanitization, WCAG 2.1 AA screen reader support, WebView fallback', color: DemoColors.success, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const _QualityHubPage())), ), _buildDemoCard( context, - icon: Icons.business_center, + icon: Icons.business_center_outlined, title: 'Enterprise Features', subtitle: - 'GPU resource safety, error routing, device tuning, deeplink security, zoom modes', - color: const Color(0xFF1A237E), + 'GPU safety, error routing, memory pressure handling, deeplink security, zoom', + color: DemoColors.success, onTap: () => Navigator.push( context, MaterialPageRoute( @@ -362,15 +371,15 @@ class DemoHomePage extends StatelessWidget { Widget _buildWhyCard(BuildContext context) { return Container( decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.amber.shade700, Colors.orange.shade600], + gradient: const LinearGradient( + colors: [Color(0xFF0D3B8E), Color(0xFF1A56DB)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( - color: Colors.orange.withValues(alpha: 0.35), + color: const Color(0xFF1A56DB).withValues(alpha: 0.35), blurRadius: 16, offset: const Offset(0, 4), ), @@ -384,11 +393,19 @@ class DemoHomePage extends StatelessWidget { MaterialPageRoute(builder: (_) => const WhyHyperRenderDemo())), borderRadius: BorderRadius.circular(14), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), child: Row( children: [ - const Icon(Icons.emoji_events, - color: Color(0xFF4E2600), size: 32), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.compare_arrows_rounded, + color: Colors.white, size: 22), + ), const SizedBox(width: 14), const Expanded( child: Column( @@ -397,17 +414,17 @@ class DemoHomePage extends StatelessWidget { Text( 'Why HyperRender?', style: TextStyle( - color: Color(0xFF4E2600), - fontSize: 16, + color: Colors.white, + fontSize: 15, fontWeight: FontWeight.w800, letterSpacing: -0.2, ), ), - SizedBox(height: 2), + SizedBox(height: 3), Text( - 'Live demos ยท Feature matrix ยท 16/16 score vs other libraries', + 'Live feature demos ยท side-by-side comparison ยท 16 vs 3 exclusive features', style: TextStyle( - color: Color(0xFF7A3E00), + color: Color(0xFFB8CEFC), fontSize: 12, height: 1.4), ), @@ -417,15 +434,17 @@ class DemoHomePage extends StatelessWidget { const SizedBox(width: 8), Container( padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + const EdgeInsets.symmetric(horizontal: 9, vertical: 5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.12), + color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.3), width: 1), ), child: const Text( - '16/16', + '16 / 16', style: TextStyle( - color: Color(0xFF4E2600), + color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13), ), @@ -439,21 +458,20 @@ class DemoHomePage extends StatelessWidget { } Widget _buildHeader(BuildContext context) { - final primary = Theme.of(context).colorScheme.primary; return Container( padding: const EdgeInsets.all(24), - decoration: BoxDecoration( + decoration: const BoxDecoration( gradient: LinearGradient( - colors: [primary, Color.lerp(primary, Colors.purple.shade800, 0.55)!], + colors: [Color(0xFF0D3B8E), Color(0xFF1A56DB)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.all(Radius.circular(20)), boxShadow: [ BoxShadow( - color: primary.withValues(alpha: 0.35), + color: Color(0x591A56DB), blurRadius: 24, - offset: const Offset(0, 8), + offset: Offset(0, 8), ), ], ), @@ -467,11 +485,22 @@ class DemoHomePage extends StatelessWidget { width: 52, height: 52, decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.25), + color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(14), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), width: 1), + ), + child: const Center( + child: Text( + 'HR', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w900, + letterSpacing: 1, + ), + ), ), - child: const Icon(Icons.rocket_launch, - color: Colors.white, size: 30), ), const SizedBox(width: 16), const Expanded( @@ -487,13 +516,13 @@ class DemoHomePage extends StatelessWidget { letterSpacing: -0.5, ), ), - SizedBox(height: 2), + SizedBox(height: 3), Text( - 'Universal Content Engine for Flutter', + 'The only Flutter HTML renderer\nwith CSS float layout.', style: TextStyle( fontSize: 13, - color: Colors.white, - letterSpacing: 0.1, + color: Color(0xFFB8CEFC), + height: 1.45, ), ), ], @@ -502,15 +531,30 @@ class DemoHomePage extends StatelessWidget { ], ), const SizedBox(height: 20), + // โ”€โ”€ Stat row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Row( + children: [ + _buildStatChip('1 646', 'tests'), + const SizedBox(width: 8), + _buildStatChip('60 FPS', 'scroll'), + const SizedBox(width: 8), + _buildStatChip('<100 ms', 'parse'), + const SizedBox(width: 8), + _buildStatChip('0', 'Gradle config'), + ], + ), + const SizedBox(height: 14), + // โ”€โ”€ Feature chips โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Wrap( spacing: 8, runSpacing: 8, children: [ - _buildChip('Float Layout', Icons.view_quilt_rounded), - _buildChip('Text Selection', Icons.select_all_rounded), - _buildChip('Ruby', Icons.translate_rounded), - _buildChip('Flexbox', Icons.view_column_rounded), - _buildChip('Widget Injection', Icons.widgets_rounded), + _buildChip('CSS Float', Icons.view_quilt_rounded), + _buildChip('Ruby / CJK', Icons.translate_rounded), + _buildChip('@keyframes', Icons.animation_rounded), + _buildChip('CSS Grid', Icons.grid_view_rounded), + _buildChip('Selection', Icons.select_all_rounded), + _buildChip('Markdown', Icons.description_outlined), ], ), ], @@ -518,14 +562,49 @@ class DemoHomePage extends StatelessWidget { ); } + Widget _buildStatChip(String value, String label) { + return Expanded( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), width: 1), + ), + child: Column( + children: [ + Text( + value, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w800, + letterSpacing: -0.3, + ), + ), + const SizedBox(height: 1), + Text( + label, + style: const TextStyle( + color: Color(0xFFB8CEFC), + fontSize: 10, + ), + ), + ], + ), + ), + ); + } + Widget _buildChip(String label, IconData icon) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.25), + color: Colors.white.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), border: - Border.all(color: Colors.white.withValues(alpha: 0.4), width: 1), + Border.all(color: Colors.white.withValues(alpha: 0.25), width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -586,61 +665,78 @@ class DemoHomePage extends StatelessWidget { borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 8, + color: Colors.black.withValues(alpha: 0.06), + blurRadius: 10, offset: const Offset(0, 2), ), ], ), - child: Material( - color: Colors.transparent, + child: ClipRRect( borderRadius: BorderRadius.circular(14), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(14), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - child: Row( - children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: color.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(12), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Left accent bar + Container( + width: 4, + color: color, ), - child: Icon(icon, color: color, size: 24), - ), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A2E), - letterSpacing: -0.1, - ), - ), - const SizedBox(height: 3), - Text( - subtitle, - style: TextStyle( - fontSize: 13, - color: Colors.grey.shade500, - height: 1.3, - ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, vertical: 14), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: color.withValues(alpha: 0.10), + borderRadius: BorderRadius.circular(11), + ), + child: Icon(icon, color: color, size: 22), + ), + const SizedBox(width: 13), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Color(0xFF0D1B3E), + letterSpacing: -0.1, + ), + ), + const SizedBox(height: 3), + Text( + subtitle, + style: TextStyle( + fontSize: 12.5, + color: Colors.grey.shade500, + height: 1.35, + ), + ), + ], + ), + ), + const SizedBox(width: 6), + Icon(Icons.chevron_right_rounded, + color: Colors.grey.shade300, size: 20), + ], ), - ], + ), ), - ), - const SizedBox(width: 8), - Icon(Icons.chevron_right_rounded, - color: Colors.grey.shade400, size: 22), - ], + ], + ), ), ), ), diff --git a/example/lib/manga_demo.dart b/example/lib/manga_demo.dart index 53f7134..f261bc6 100644 --- a/example/lib/manga_demo.dart +++ b/example/lib/manga_demo.dart @@ -80,86 +80,89 @@ class _CharacterTab extends StatelessWidget { // gap replaced by margin-right on children for compatibility. static const _html = ''' @@ -267,9 +270,17 @@ class _CharacterTab extends StatelessWidget { @override Widget build(BuildContext context) { - return HyperViewer( - html: _html, - selectable: true, + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 16, + ), + child: HyperViewer(html: _html, selectable: true), + ), + ), ); } } @@ -283,42 +294,44 @@ class _SynopsisTab extends StatelessWidget { static const _html = '''

        @@ -423,9 +436,17 @@ class _SynopsisTab extends StatelessWidget { @override Widget build(BuildContext context) { - return HyperViewer( - html: _html, - selectable: true, + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 16, + ), + child: HyperViewer(html: _html, selectable: true), + ), + ), ); } } @@ -441,43 +462,43 @@ class _PanelsTab extends StatelessWidget { // rock-solid. Flex with nested children collapses to zero-height panels. // Dark background (#212121) is inside the HTML wrapper, not Flutter-level. static const _html = ''' -
        +
        -
        - +
        + ๅฝฑ็•Œๅนดไปฃ่จ˜ โ–ธ Ch.12 โ–ธ P.8
        -
        +
        -
        -
        ๐Ÿฏ
        -
        +
        +
        ๐Ÿฏ
        +
        ้ฌผๆฎบ้šŠใใ•ใคใŸใ„ๆœฌ้ƒจโ€”โ€”ๆทฑๅคœ
        -
        +
        -
        -
        ๐Ÿ˜ค
        -
        +
        +
        ๐Ÿ˜ค
        +
        ใ€Œๆฅใใ‚‹ใŒใ„ใ„โ€ฆโ€ฆไธŠๅผฆใ˜ใ‚‡ใ†ใ’ใ‚“ใ‚ˆใ€
        -
        -
        ใƒ‰ใƒ‰ใƒ‰ใ‚ฉ๏ผ๏ผ
        +
        +
        ใƒ‰ใƒ‰ใƒ‰ใ‚ฉ๏ผ๏ผ
        -
        -
        ๐Ÿ˜ˆ
        -
        +
        +
        ๐Ÿ˜ˆ
        +
        ใ€Œ้ข็™ฝใŠใ‚‚ใ—ใ‚ใ„โ€ฆโ€ฆ๏ผใ€
        @@ -486,34 +507,34 @@ class _PanelsTab extends StatelessWidget {
        -
        -
        - โš”๏ธ - ใ‚บใƒใƒƒ๏ผ๏ผ - โš”๏ธ +
        +
        + โš”๏ธ + ใ‚บใƒใƒƒ๏ผ๏ผ + โš”๏ธ
        -
        +
        ๅฝฑใ‹ใ’ใƒŽๅž‹ใ‹ใŸโ€”โ€” - ๆผ†้ป’ใ—ใฃใ“ใใฎๆ–ฌ้–ƒใ–ใ‚“ใ›ใ‚“๏ผ๏ผ + ๆผ†้ป’ใ—ใฃใ“ใใฎๆ–ฌ้–ƒใ–ใ‚“ใ›ใ‚“๏ผ๏ผ
        -
        +
        -
        -
        -
        ๐Ÿ˜ฑ
        -
        ใ€Œใพใ•ใ‹โ€”โ€”๏ผใ€
        +
        +
        +
        ๐Ÿ˜ฑ
        +
        ใ€Œใพใ•ใ‹โ€”โ€”๏ผใ€
        -
        -
        ใƒใ‚ญใƒƒ๏ผ
        +
        +
        ใƒใ‚ญใƒƒ๏ผ
        -
        -
        +
        +
        ๏ผˆ็ˆถใกใกใ•ใ‚“โ€ฆโ€ฆ๏ผ‰
        @@ -521,8 +542,8 @@ class _PanelsTab extends StatelessWidget {
        -
        - ใคใƒปใฅใƒปใโ€ฆโ€ฆ +
        + ใคใƒปใฅใƒปใโ€ฆโ€ฆ
        @@ -532,9 +553,17 @@ class _PanelsTab extends StatelessWidget { @override Widget build(BuildContext context) { - return HyperViewer( - html: _html, - selectable: false, + return Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + child: HyperViewer( + html: _html, + selectable: false, + ), + ), + ), ); } } @@ -548,32 +577,35 @@ class _FuriganaTab extends StatelessWidget { static const _html = '''
        @@ -679,9 +711,17 @@ class _FuriganaTab extends StatelessWidget { ), ), Expanded( - child: HyperViewer( - html: _html, - selectable: true, + child: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: SingleChildScrollView( + child: HyperViewer( + html: _html, + selectable: true, + ), + ), + ), ), ), ], diff --git a/example/lib/stress_test_demo.dart b/example/lib/stress_test_demo.dart index 93dd0b8..dfc5052 100644 --- a/example/lib/stress_test_demo.dart +++ b/example/lib/stress_test_demo.dart @@ -255,7 +255,7 @@ class _StressTestDemoState extends State { .tale-section { padding:0 16px 28px 16px; } .tale-heading { color:#6B2D0C; font-size:20px; margin:0 0 14px 0; padding-bottom:8px; border-bottom:2px solid #D4B896; font-style:italic; } .tale-emoji { color:#C19A6B; padding-right:6px; } - .tale-img { display:block; margin:0 auto 18px auto; border-radius:10px; border:3px solid #D4B896; } + .tale-img { display:block; max-width:100%; margin:0 auto 18px auto; border-radius:10px; border:3px solid #D4B896; } .tale-caption { text-align:center; color:#C19A6B; font-size:12px; margin:-12px 0 16px 0; font-style:italic; } .tale-para { margin:0 0 14px 0; font-size:15px; text-align:justify; color:#3a2010; } .tale-quote { border-left:4px solid #C19A6B; margin:16px 0; padding:12px 16px; background:#FEF9F0; border-radius:0 8px 8px 0; } @@ -328,11 +328,11 @@ class _StressTestDemoState extends State { .novel-drop { font-size:48px; font-weight:bold; color:#1565C0; float:left; line-height:0.85; margin:6px 8px 0 0; } .novel-highlight { background:#E3F2FD; padding:12px 16px; border-radius:8px; margin:16px 0; border-left:4px solid #1976D2; } .novel-highlight strong { color:#1565C0; } - .novel-img { display:block; margin:0 auto 16px auto; border-radius:10px; } + .novel-img { display:block; max-width:100%; margin:0 auto 16px auto; border-radius:10px; } .novel-hr { margin:28px 0; border:none; border-top:1px solid #E0E0E0; } .novel-tag { display:inline-block; background:#E3F2FD; color:#1565C0; border-radius:12px; padding:2px 8px; font-size:11px; margin:0 4px 4px 0; } -
        +

        ๐Ÿ“– The Infinite Archive

        $pages chapters ยท Performance & rendering showcase

        @@ -978,12 +978,17 @@ class _StressTestDemoState extends State { switch (_selectedLibrary) { case 'HyperRender': - return HyperViewer( - html: content, - mode: HyperRenderMode.auto, - selectable: true, - placeholderBuilder: (context) => - _buildLoadingPlaceholder(content.length), + // Manga is intentionally edge-to-edge; other content types need margin. + final hPad = _contentType == _ContentType.manga ? 0.0 : 16.0; + return Padding( + padding: EdgeInsets.symmetric(horizontal: hPad), + child: HyperViewer( + html: content, + mode: HyperRenderMode.auto, + selectable: true, + placeholderBuilder: (context) => + _buildLoadingPlaceholder(content.length), + ), ); case 'flutter_html': return ClipRect( diff --git a/example/lib/why_hyper_render_demo.dart b/example/lib/why_hyper_render_demo.dart index e50beb9..19bdf32 100644 --- a/example/lib/why_hyper_render_demo.dart +++ b/example/lib/why_hyper_render_demo.dart @@ -347,15 +347,15 @@ class WhyHyperRenderDemo extends StatelessWidget { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( - gradient: LinearGradient( - colors: [DemoColors.primary, const Color(0xFF6A1B9A)], + gradient: const LinearGradient( + colors: [Color(0xFF0D3B8E), Color(0xFF1A56DB)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: DemoColors.primary.withValues(alpha: 0.4), + color: const Color(0xFF1A56DB).withValues(alpha: 0.4), blurRadius: 24, offset: const Offset(0, 8), ), @@ -367,13 +367,25 @@ class WhyHyperRenderDemo extends StatelessWidget { Row( children: [ Container( - padding: const EdgeInsets.all(10), + width: 50, + height: 50, decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.25), + color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), width: 1), + ), + child: const Center( + child: Text( + 'HR', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w900, + letterSpacing: 1, + ), + ), ), - child: const Icon(Icons.emoji_events, - color: Colors.white, size: 28), ), const SizedBox(width: 14), const Expanded( @@ -381,19 +393,22 @@ class WhyHyperRenderDemo extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'The Best Flutter HTML Renderer', + 'CSS float. No other Flutter\nHTML library can do this.', style: TextStyle( color: Colors.white, - fontSize: 18, + fontSize: 17, fontWeight: FontWeight.w800, - letterSpacing: -0.3, + letterSpacing: -0.4, + height: 1.3, ), ), + SizedBox(height: 4), Text( - 'Custom RenderObject ยท Zero WebView ยท Zero Deps', + 'Single RenderObject ยท Zero WebView ยท XSS-safe by default', style: TextStyle( - color: Colors.white, + color: Color(0xFFB8CEFC), fontSize: 12, + height: 1.4, ), ), ], @@ -518,20 +533,20 @@ class WhyHyperRenderDemo extends StatelessWidget { return Row( children: [ Expanded( - child: _buildStatCard( - '16 / 16', 'Features', Icons.check_circle, Colors.green)), + child: _buildStatCard('1 646', 'Tests passing', + Icons.check_circle_outline, const Color(0xFF2E7D32))), const SizedBox(width: 10), Expanded( - child: - _buildStatCard('60+', 'CSS Props', Icons.style, Colors.indigo)), + child: _buildStatCard( + '60 FPS', 'Scroll', Icons.speed, Colors.indigo)), const SizedBox(width: 10), Expanded( - child: _buildStatCard( - 'Zero', 'WebViews', Icons.no_encryption, Colors.red)), + child: _buildStatCard('<100ms', 'Parse', Icons.timer_outlined, + const Color(0xFFBF360C))), const SizedBox(width: 10), Expanded( child: _buildStatCard( - '3+', 'Formats', Icons.data_object, Colors.teal)), + '8 MB', 'RAM (25k)', Icons.memory, const Color(0xFF00695C))), ], ); } diff --git a/example/lib/zero_padding_image_demo.dart b/example/lib/zero_padding_image_demo.dart new file mode 100644 index 0000000..77f6f94 --- /dev/null +++ b/example/lib/zero_padding_image_demo.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:hyper_render/hyper_render.dart'; + +class ZeroPaddingImageDemo extends StatelessWidget { + const ZeroPaddingImageDemo({super.key}); + + static const html = ''' + + +
        +
        +

        Edge-to-Edge Typography

        + +
        + + + Main hero + +
        +

        + HyperRender 2.0 now supports true edge-to-edge images. + By setting _kImageMargin to 0.0 in the core engine, + width: 100% images now fill the entire available width of their container. +

        + +
        + Developer Tip: To create the "Modern Reader" look, wrap your HyperViewer + without any Flutter-level padding, and handle horizontal spacing entirely within your CSS. +
        + +

        + In this demo, the header and this text section have 20px of horizontal padding defined in CSS, + while the hero image above and the secondary image below span the full width of your device. +

        +
        + + + Mid article image + +
        +

        + Below is a standard image. It is placed inside a container with padding, + so it respects the margins. We've also added a border-radius and box-shadow + to demonstrate high-end visual styling. +

        + + + Standard image +
        Figure 1: A standard image respecting container padding.
        + +

        + The combination of immersive full-bleed imagery and perfectly aligned typography + makes HyperRender the choice for premium publishing applications. +

        + +
        +
        +
        +'''; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF8F9FA), + appBar: AppBar( + title: const Text('Edge-to-Edge Layout'), + backgroundColor: const Color(0xFF1A56DB), + foregroundColor: Colors.white, + elevation: 0, + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: const HyperViewer( + html: html, + selectable: true, + ), + ), + ), + ); + } +} diff --git a/packages/hyper_render_clipboard/CHANGELOG.md b/packages/hyper_render_clipboard/CHANGELOG.md index 9c66ad4..df6ee74 100644 --- a/packages/hyper_render_clipboard/CHANGELOG.md +++ b/packages/hyper_render_clipboard/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog โ€” hyper_render_clipboard +## [1.3.1] - 2026-05-14 + +### ๐Ÿ—๏ธ Packaging +- **Opt-in add-on**: `hyper_render_clipboard` is no longer bundled with the root `hyper_render` package. Add it explicitly to your `pubspec.yaml` if you use `SuperClipboardHandler`. This removes the `compileSdk = 34` requirement from default `hyper_render` users. + ## [1.3.0] - 2026-05-03 ### โœจ New Features diff --git a/packages/hyper_render_clipboard/README.md b/packages/hyper_render_clipboard/README.md index 015bffc..dabcbfc 100644 --- a/packages/hyper_render_clipboard/README.md +++ b/packages/hyper_render_clipboard/README.md @@ -8,7 +8,7 @@ Image clipboard operations for [HyperRender](https://pub.dev/packages/hyper_rend ```yaml dependencies: - hyper_render_clipboard: ^1.3.0 + hyper_render_clipboard: ^1.3.1 ``` --- @@ -78,6 +78,23 @@ PNG, JPEG, GIF, WebP, BMP, TIFF --- +## HyperRender Ecosystem + +| Package | Description | +|---------|-------------| +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering | +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API | +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser | +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser | +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| **[hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard)** | **Image copy / save / share** โ† you are here |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_clipboard) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        +
        +---
        +
         ## License
         
         MIT โ€” see [LICENSE](LICENSE).
        diff --git a/packages/hyper_render_core/CHANGELOG.md b/packages/hyper_render_core/CHANGELOG.md
        index e8afea7..b9d3a12 100644
        --- a/packages/hyper_render_core/CHANGELOG.md
        +++ b/packages/hyper_render_core/CHANGELOG.md
        @@ -1,5 +1,22 @@
         # Changelog โ€” hyper_render_core
         
        +## [1.3.1] - 2026-05-14
        +
        +### โœจ New CSS Properties
        +- **`list-style-type`**: All 11 values โ€” `disc`, `circle`, `square`, `decimal`, `decimal-leading-zero`, `lower-alpha`, `upper-alpha`, `lower-latin`, `upper-latin`, `lower-roman`, `upper-roman`, `none`
        +- **`list-style-position`**: `inside` / `outside` (default)
        +- **`list-style` shorthand**: parses ` ` in any order
        +- **`background-repeat`**: `repeat`, `repeat-x`, `repeat-y`, `no-repeat`, `space`, `round`
        +- **`background-position`**: keyword (`center`, `top left`, etc.) and percentage values
        +
        +### ๐Ÿš€ Performance
        +- **Selection rects cached**: `getSelectionRects()` called once per drag event (was 3ร—); stored in `_selectionRects` field โ€” eliminates redundant layout walks during selection drag
        +- **Auto-scroll proportional speed**: `_autoScrollIfNearEdge` now scales 0โ€“20 px/frame based on finger distance from edge (was fixed 15 px/frame)
        +- **`HyperTeardropHandlePainter` deduplicated**: renamed to `HyperTeardropHandlePainter`, made public, and exported from core; duplicate in the virtualized overlay deleted
        +
        +### ๐Ÿ› Bug Fixes
        +- **Edge-to-edge images**: `_kImageMargin` set to `0.0` โ€” `width: 100%` images now truly fill their container with no internal margin offset
        +
         ## [1.3.0] - 2026-05-03
         
         ### โœจ New Features
        diff --git a/packages/hyper_render_core/README.md b/packages/hyper_render_core/README.md
        index 9fa3ad1..3112221 100644
        --- a/packages/hyper_render_core/README.md
        +++ b/packages/hyper_render_core/README.md
        @@ -10,7 +10,7 @@ Most apps should depend on [`hyper_render`](https://pub.dev/packages/hyper_rende
         
         ```yaml
         dependencies:
        -  hyper_render_core: ^1.3.0
        +  hyper_render_core: ^1.3.1
         ```
         
         ---
        @@ -51,12 +51,12 @@ Register a `HyperNodePlugin` to render arbitrary HTML tags as Flutter widgets:
         
         ```dart
         class MyCardPlugin implements HyperNodePlugin {
        -  @override String get tagName => 'my-card';
        +  @override List get tagNames => ['my-card'];
           @override bool get isInline => false; // true = flows with text
         
           @override
        -  Widget? build(HyperPluginBuildContext ctx) {
        -    return Card(child: Text(ctx.node.textContent));
        +  Widget? buildWidget(UDTNode node, HyperPluginBuildContext ctx) {
        +    return Card(child: Text(node.textContent));
           }
         }
         
        @@ -97,6 +97,23 @@ Implement these to extend the engine:
         
         ---
         
        +## HyperRender Ecosystem
        +
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| **[hyper_render_core](https://pub.dev/packages/hyper_render_core)** | **Core engine: UDT model, `RenderHyperBox`, plugin API** โ† you are here |
        +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser |
        +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser |
        +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_core) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        +
        +---
        +
         ## License
         
         MIT โ€” see [LICENSE](LICENSE).
        diff --git a/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart b/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart
        index b694c29..c368295 100644
        --- a/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart
        +++ b/packages/hyper_render_core/lib/src/core/render_hyper_box_layout.dart
        @@ -7,7 +7,7 @@ const double _kDefaultFloatMargin = 8.0;
         
         const double _kMinFloatYStep = 1.0;
         const double _kImageMargin =
        -    32.0; // horizontal margin subtracted from maxWidth for images
        +    0.0; // horizontal margin subtracted from maxWidth for images
         const double _kDefaultFlexFallbackHeight = 50.0;
         const double _kDefaultTableFallbackHeight = 200.0;
         const double _kTableBottomMargin = 16.0;
        diff --git a/packages/hyper_render_devtools/CHANGELOG.md b/packages/hyper_render_devtools/CHANGELOG.md
        index 356b0f0..9e530cd 100644
        --- a/packages/hyper_render_devtools/CHANGELOG.md
        +++ b/packages/hyper_render_devtools/CHANGELOG.md
        @@ -1,5 +1,10 @@
         # Changelog โ€” hyper_render_devtools
         
        +## [1.3.1] - 2026-05-14
        +
        +### ๐Ÿ—๏ธ Maintenance
        +- Updated `hyper_render_core` dependency to `^1.3.1`
        +
         ## [1.3.0] - 2026-05-03
         
         ### โœจ New Features
        diff --git a/packages/hyper_render_devtools/README.md b/packages/hyper_render_devtools/README.md
        index b25b87d..4ce19f6 100644
        --- a/packages/hyper_render_devtools/README.md
        +++ b/packages/hyper_render_devtools/README.md
        @@ -17,14 +17,14 @@ Add the package to your app's `dev_dependencies` (debug-only usage):
         
         ```yaml
         dev_dependencies:
        -  hyper_render_devtools: ^1.3.0
        +  hyper_render_devtools: ^1.3.1
         ```
         
         Or add to `dependencies` if you want it available in profile builds:
         
         ```yaml
         dependencies:
        -  hyper_render_devtools: ^1.3.0
        +  hyper_render_devtools: ^1.3.1
         ```
         
         ## Usage
        @@ -108,4 +108,17 @@ The `devtools_ui` sub-directory contains the Flutter Web source for the panel UI
         
         ---
         
        -[hyper_render](https://pub.dev/packages/hyper_render) ยท [Issue tracker](https://github.com/brewkits/hyper_render/issues) ยท [Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_devtools)
        +## HyperRender Ecosystem
        +
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API |
        +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser |
        +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser |
        +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| **[hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools)** | **Flutter DevTools inspector** โ† you are here |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_devtools) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        diff --git a/packages/hyper_render_highlight/CHANGELOG.md b/packages/hyper_render_highlight/CHANGELOG.md
        index baa41f5..23a6362 100644
        --- a/packages/hyper_render_highlight/CHANGELOG.md
        +++ b/packages/hyper_render_highlight/CHANGELOG.md
        @@ -1,5 +1,10 @@
         # Changelog โ€” hyper_render_highlight
         
        +## [1.3.1] - 2026-05-14
        +
        +### ๐Ÿ—๏ธ Maintenance
        +- Updated `hyper_render_core` dependency to `^1.3.1`
        +
         ## [1.3.0] - 2026-05-03
         
         ### โœจ New Features
        diff --git a/packages/hyper_render_highlight/README.md b/packages/hyper_render_highlight/README.md
        index 77abace..cab0b0b 100644
        --- a/packages/hyper_render_highlight/README.md
        +++ b/packages/hyper_render_highlight/README.md
        @@ -8,8 +8,8 @@ Syntax highlighting plugin for [HyperRender](https://pub.dev/packages/hyper_rend
         
         ```yaml
         dependencies:
        -  hyper_render_core: ^1.3.0
        -  hyper_render_highlight: ^1.3.0
        +  hyper_render_core: ^1.3.1
        +  hyper_render_highlight: ^1.3.1
         ```
         
         ---
        @@ -75,6 +75,23 @@ print(FlutterHighlightCodeHighlighter().supportedLanguages);
         
         ---
         
        +## HyperRender Ecosystem
        +
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API |
        +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser |
        +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser |
        +| **[hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight)** | **Syntax highlighting for `` / `
        ` blocks** โ† you are here |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_highlight) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        +
        +---
        +
         ## License
         
         MIT โ€” see [LICENSE](LICENSE).
        diff --git a/packages/hyper_render_html/CHANGELOG.md b/packages/hyper_render_html/CHANGELOG.md
        index 989f04a..3f6af3c 100644
        --- a/packages/hyper_render_html/CHANGELOG.md
        +++ b/packages/hyper_render_html/CHANGELOG.md
        @@ -1,5 +1,10 @@
         # Changelog โ€” hyper_render_html
         
        +## [1.3.1] - 2026-05-14
        +
        +### ๐Ÿ—๏ธ Maintenance
        +- Updated `hyper_render_core` dependency to `^1.3.1`
        +
         ## [1.3.0] - 2026-05-03
         
         ### โœจ New Features
        diff --git a/packages/hyper_render_html/README.md b/packages/hyper_render_html/README.md
        index 26d5d25..9a667b8 100644
        --- a/packages/hyper_render_html/README.md
        +++ b/packages/hyper_render_html/README.md
        @@ -8,8 +8,8 @@ HTML parsing plugin for [HyperRender](https://pub.dev/packages/hyper_render). Co
         
         ```yaml
         dependencies:
        -  hyper_render_core: ^1.3.0
        -  hyper_render_html: ^1.3.0
        +  hyper_render_core: ^1.3.1
        +  hyper_render_html: ^1.3.1
         ```
         
         ---
        @@ -76,12 +76,31 @@ final document = HtmlContentParser().parseWithOptions(
         
         **Layout**: `display` (block, inline, inline-block, flex, grid, none), `float`, `clear`, `overflow`, `position` (static, relative)
         
        -**Visual**: `background-color`, `background-image`, `opacity`, `transform`, `filter`, `backdrop-filter`
        +**List**: `list-style-type` (disc/circle/square/decimal/lower|upper-alpha|latin|roman/none), `list-style-position`, `list-style` shorthand
        +
        +**Visual**: `background-color`, `background-image`, `background-repeat`, `background-position`, `opacity`, `transform`, `filter`, `backdrop-filter`
         
         **CSS features**: custom properties (`var()`), `calc()`, `@keyframes`, specificity cascade, inheritance
         
         ---
         
        +## HyperRender Ecosystem
        +
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API |
        +| **[hyper_render_html](https://pub.dev/packages/hyper_render_html)** | **HTML + CSS โ†’ UDT parser** โ† you are here |
        +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser |
        +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_html) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        +
        +---
        +
         ## License
         
         MIT โ€” see [LICENSE](LICENSE).
        diff --git a/packages/hyper_render_markdown/CHANGELOG.md b/packages/hyper_render_markdown/CHANGELOG.md
        index 593f6cd..0c8cb8e 100644
        --- a/packages/hyper_render_markdown/CHANGELOG.md
        +++ b/packages/hyper_render_markdown/CHANGELOG.md
        @@ -1,5 +1,10 @@
         # Changelog โ€” hyper_render_markdown
         
        +## [1.3.1] - 2026-05-14
        +
        +### ๐Ÿ—๏ธ Maintenance
        +- Updated `hyper_render_core` dependency to `^1.3.1`
        +
         ## [1.3.0] - 2026-05-03
         
         ### โœจ New Features
        diff --git a/packages/hyper_render_markdown/README.md b/packages/hyper_render_markdown/README.md
        index 28ad617..068e1fb 100644
        --- a/packages/hyper_render_markdown/README.md
        +++ b/packages/hyper_render_markdown/README.md
        @@ -8,8 +8,8 @@ Markdown parsing plugin for [HyperRender](https://pub.dev/packages/hyper_render)
         
         ```yaml
         dependencies:
        -  hyper_render_core: ^1.3.0
        -  hyper_render_markdown: ^1.3.0
        +  hyper_render_core: ^1.3.1
        +  hyper_render_markdown: ^1.3.1
         ```
         
         ---
        @@ -72,6 +72,23 @@ HyperRenderWidget(
         
         ---
         
        +## HyperRender Ecosystem
        +
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API |
        +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser |
        +| **[hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown)** | **Markdown (GFM) โ†’ UDT parser** โ† you are here |
        +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| [hyper_render_math](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML rendering *(opt-in)* |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_markdown) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md)
        +
        +---
        +
         ## License
         
         MIT โ€” see [LICENSE](LICENSE).
        diff --git a/packages/hyper_render_math/CHANGELOG.md b/packages/hyper_render_math/CHANGELOG.md
        index 0222dc7..b8a199d 100644
        --- a/packages/hyper_render_math/CHANGELOG.md
        +++ b/packages/hyper_render_math/CHANGELOG.md
        @@ -1,5 +1,11 @@
         # Changelog โ€” hyper_render_math
         
        +## [1.3.1] - 2026-05-14
        +
        +### ๐Ÿ—๏ธ Packaging
        +- **Opt-in add-on**: `hyper_render_math` is no longer bundled with the root `hyper_render` package. Add it explicitly to your `pubspec.yaml` to use `MathNodePlugin` / `LatexNodePlugin`.
        +- Updated `hyper_render_core` dependency to `^1.3.1`
        +
         ## [1.3.0] - 2026-05-03
         
         - Initial release: skeleton plugin for `` and `` tag rendering.
        diff --git a/packages/hyper_render_math/README.md b/packages/hyper_render_math/README.md
        index d7d1ff8..cdc5f2d 100644
        --- a/packages/hyper_render_math/README.md
        +++ b/packages/hyper_render_math/README.md
        @@ -9,8 +9,7 @@ Renders `` and `` tags using [flutter_math_fork](https://pub.dev/pa
         
         ```yaml
         dependencies:
        -  hyper_render_core: ^1.3.0
        -  hyper_render_math: ^1.3.0
        +  hyper_render_math: ^1.3.1
         ```
         
         ---
        @@ -64,7 +63,23 @@ so you can identify and fix the malformed formula.
         
         ---
         
        -## Contributing
        +## HyperRender Ecosystem
         
        -See [PLUGIN_DEVELOPMENT.md](../../doc/PLUGIN_DEVELOPMENT.md) for the full guide
        -on building, testing, and submitting plugins.
        +| Package | Description |
        +|---------|-------------|
        +| [hyper_render](https://pub.dev/packages/hyper_render) | Main package โ€” `HyperViewer` widget, HTML + Markdown rendering |
        +| [hyper_render_core](https://pub.dev/packages/hyper_render_core) | Core engine: UDT model, `RenderHyperBox`, plugin API |
        +| [hyper_render_html](https://pub.dev/packages/hyper_render_html) | HTML + CSS โ†’ UDT parser |
        +| [hyper_render_markdown](https://pub.dev/packages/hyper_render_markdown) | Markdown (GFM) โ†’ UDT parser |
        +| [hyper_render_highlight](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `` / `
        ` blocks |
        +| [hyper_render_clipboard](https://pub.dev/packages/hyper_render_clipboard) | Image copy / save / share *(opt-in)* |
        +| **[hyper_render_math](https://pub.dev/packages/hyper_render_math)** | **LaTeX / MathML rendering** โ† you are here |
        +| [hyper_render_devtools](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools inspector |
        +
        +[Source](https://github.com/brewkits/hyper_render/tree/main/packages/hyper_render_math) ยท [Issues](https://github.com/brewkits/hyper_render/issues) ยท [Changelog](CHANGELOG.md) ยท [Plugin Development Guide](https://github.com/brewkits/hyper_render/blob/main/doc/PLUGIN_DEVELOPMENT.md)
        +
        +---
        +
        +## License
        +
        +MIT โ€” see [LICENSE](LICENSE).
        diff --git a/scripts/prepare_publish.sh b/scripts/prepare_publish.sh
        index 664c173..b0925ae 100755
        --- a/scripts/prepare_publish.sh
        +++ b/scripts/prepare_publish.sh
        @@ -77,7 +77,7 @@ else
           ok "pubspec.yaml already has version deps"
         fi
         
        -# Swap path: ../hyper_render_core โ†’ hyper_render_core: ^1.3.0 in sub-packages
        +# Swap path: ../hyper_render_core โ†’ hyper_render_core: ^1.3.1 in sub-packages
         # BSD sed (macOS) requires '' after -i; GNU sed does not
         _sed() { sed -i '' "$@" 2>/dev/null || sed -i "$@"; }
         
        @@ -89,7 +89,7 @@ for f in packages/hyper_render_html/pubspec.yaml \
                   packages/hyper_render_math/pubspec.yaml; do
           if [ -f "$f" ] && grep -q "path: \.\./hyper_render_core" "$f"; then
             _sed '/hyper_render_core:/{n;/path: \.\.\/hyper_render_core/d;}' "$f"
        -    _sed 's|hyper_render_core:$|hyper_render_core: ^1.3.0|' "$f"
        +    _sed 's|hyper_render_core:$|hyper_render_core: ^1.3.1|' "$f"
             ok "Swapped path dep โ†’ version dep in $f"
           fi
         done
        
        From 1c3b18a7167f1c9c53f5d0b7f154e455523816f6 Mon Sep 17 00:00:00 2001
        From: =?UTF-8?q?Nguye=CC=82=CC=83n=20Tua=CC=82=CC=81n=20Vie=CC=A3=CC=82t?=
         
        Date: Thu, 14 May 2026 19:29:30 +0700
        Subject: [PATCH 7/7] chore(example): update generated lockfiles after removing
         hyper_render_clipboard dep
        
        ---
         example/ios/Podfile.lock                      | 18 -----
         .../flutter/generated_plugin_registrant.cc    |  8 ---
         example/linux/flutter/generated_plugins.cmake |  2 -
         .../Flutter/GeneratedPluginRegistrant.swift   |  6 --
         example/pubspec.lock                          | 71 -------------------
         5 files changed, 105 deletions(-)
        
        diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
        index 6008ca7..3d06689 100644
        --- a/example/ios/Podfile.lock
        +++ b/example/ios/Podfile.lock
        @@ -1,11 +1,7 @@
         PODS:
           - audio_session (0.0.1):
             - Flutter
        -  - device_info_plus (0.0.1):
        -    - Flutter
           - Flutter (1.0.0)
        -  - irondash_engine_context (0.0.1):
        -    - Flutter
           - just_audio (0.0.1):
             - Flutter
             - FlutterMacOS
        @@ -16,8 +12,6 @@ PODS:
           - sqflite (0.0.3):
             - Flutter
             - FlutterMacOS
        -  - super_native_extensions (0.0.1):
        -    - Flutter
           - url_launcher_ios (0.0.1):
             - Flutter
           - video_player_avfoundation (0.0.1):
        @@ -31,14 +25,11 @@ PODS:
         
         DEPENDENCIES:
           - audio_session (from `.symlinks/plugins/audio_session/ios`)
        -  - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
           - Flutter (from `Flutter`)
        -  - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
           - just_audio (from `.symlinks/plugins/just_audio/darwin`)
           - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
           - share_plus (from `.symlinks/plugins/share_plus/ios`)
           - sqflite (from `.symlinks/plugins/sqflite/darwin`)
        -  - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
           - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
           - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
           - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
        @@ -47,12 +38,8 @@ DEPENDENCIES:
         EXTERNAL SOURCES:
           audio_session:
             :path: ".symlinks/plugins/audio_session/ios"
        -  device_info_plus:
        -    :path: ".symlinks/plugins/device_info_plus/ios"
           Flutter:
             :path: Flutter
        -  irondash_engine_context:
        -    :path: ".symlinks/plugins/irondash_engine_context/ios"
           just_audio:
             :path: ".symlinks/plugins/just_audio/darwin"
           package_info_plus:
        @@ -61,8 +48,6 @@ EXTERNAL SOURCES:
             :path: ".symlinks/plugins/share_plus/ios"
           sqflite:
             :path: ".symlinks/plugins/sqflite/darwin"
        -  super_native_extensions:
        -    :path: ".symlinks/plugins/super_native_extensions/ios"
           url_launcher_ios:
             :path: ".symlinks/plugins/url_launcher_ios/ios"
           video_player_avfoundation:
        @@ -74,14 +59,11 @@ EXTERNAL SOURCES:
         
         SPEC CHECKSUMS:
           audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
        -  device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
           Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
        -  irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
           just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
           package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
           share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
           sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
        -  super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
           url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
           video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
           wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
        diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc
        index fcafa49..f6f23bf 100644
        --- a/example/linux/flutter/generated_plugin_registrant.cc
        +++ b/example/linux/flutter/generated_plugin_registrant.cc
        @@ -6,17 +6,9 @@
         
         #include "generated_plugin_registrant.h"
         
        -#include 
        -#include 
         #include 
         
         void fl_register_plugins(FlPluginRegistry* registry) {
        -  g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
        -      fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
        -  irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
        -  g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
        -      fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
        -  super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
           g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
               fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
           url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
        diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake
        index 8a5dc51..df8d2f7 100644
        --- a/example/linux/flutter/generated_plugins.cmake
        +++ b/example/linux/flutter/generated_plugins.cmake
        @@ -3,8 +3,6 @@
         #
         
         list(APPEND FLUTTER_PLUGIN_LIST
        -  irondash_engine_context
        -  super_native_extensions
           url_launcher_linux
         )
         
        diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
        index e481378..e7a0ba1 100644
        --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
        +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
        @@ -6,13 +6,10 @@ import FlutterMacOS
         import Foundation
         
         import audio_session
        -import device_info_plus
        -import irondash_engine_context
         import just_audio
         import package_info_plus
         import share_plus
         import sqflite
        -import super_native_extensions
         import url_launcher_macos
         import video_player_avfoundation
         import wakelock_plus
        @@ -20,13 +17,10 @@ import webview_flutter_wkwebview
         
         func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
           AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
        -  DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
        -  IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
           JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
           FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
           SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
           SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
        -  SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
           UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
           FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
           WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
        diff --git a/example/pubspec.lock b/example/pubspec.lock
        index 3857b28..6646d1e 100644
        --- a/example/pubspec.lock
        +++ b/example/pubspec.lock
        @@ -137,22 +137,6 @@ packages:
               url: "https://pub.dev"
             source: hosted
             version: "0.7.12"
        -  device_info_plus:
        -    dependency: transitive
        -    description:
        -      name: device_info_plus
        -      sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "11.5.0"
        -  device_info_plus_platform_interface:
        -    dependency: transitive
        -    description:
        -      name: device_info_plus_platform_interface
        -      sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "7.0.3"
           fake_async:
             dependency: transitive
             description:
        @@ -359,13 +343,6 @@ packages:
               relative: true
             source: path
             version: "1.3.1"
        -  hyper_render_clipboard:
        -    dependency: "direct main"
        -    description:
        -      path: "../packages/hyper_render_clipboard"
        -      relative: true
        -    source: path
        -    version: "1.3.1"
           hyper_render_core:
             dependency: "direct main"
             description:
        @@ -394,22 +371,6 @@ packages:
               relative: true
             source: path
             version: "1.3.1"
        -  irondash_engine_context:
        -    dependency: transitive
        -    description:
        -      name: irondash_engine_context
        -      sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7"
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "0.5.5"
        -  irondash_message_channel:
        -    dependency: transitive
        -    description:
        -      name: irondash_message_channel
        -      sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "0.7.0"
           jni:
             dependency: transitive
             description:
        @@ -666,14 +627,6 @@ packages:
               url: "https://pub.dev"
             source: hosted
             version: "7.0.2"
        -  pixel_snap:
        -    dependency: transitive
        -    description:
        -      name: pixel_snap
        -      sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0"
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "0.1.5"
           platform:
             dependency: transitive
             description:
        @@ -791,22 +744,6 @@ packages:
               url: "https://pub.dev"
             source: hosted
             version: "1.4.1"
        -  super_clipboard:
        -    dependency: transitive
        -    description:
        -      name: super_clipboard
        -      sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "0.9.1"
        -  super_native_extensions:
        -    dependency: transitive
        -    description:
        -      name: super_native_extensions
        -      sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "0.9.1"
           synchronized:
             dependency: transitive
             description:
        @@ -1055,14 +992,6 @@ packages:
               url: "https://pub.dev"
             source: hosted
             version: "5.15.0"
        -  win32_registry:
        -    dependency: transitive
        -    description:
        -      name: win32_registry
        -      sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
        -      url: "https://pub.dev"
        -    source: hosted
        -    version: "2.1.0"
           xdg_directories:
             dependency: transitive
             description: